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

     1  package goproxy
     2  
     3  import (
     4  	"bufio"
     5  	"io"
     6  	"log"
     7  	"net"
     8  	"net/http"
     9  	"os"
    10  	"regexp"
    11  	"sync/atomic"
    12  )
    13  
    14  // The basic proxy type. Implements http.Handler.
    15  type ProxyHttpServer struct {
    16  	// session variable must be aligned in i386
    17  	// see http://golang.org/src/pkg/sync/atomic/doc.go#L41
    18  	sess int64
    19  	// setting Verbose to true will log information on each request sent to the proxy
    20  	Verbose         bool
    21  	Logger          *log.Logger
    22  	isReverseProxy  bool
    23  	NonproxyHandler http.Handler
    24  	reqHandlers     []ReqHandler
    25  	respHandlers    []RespHandler
    26  	httpsHandlers   []HttpsHandler
    27  	Tr              *http.Transport
    28  	// ConnectDial will be used to create TCP connections for CONNECT requests
    29  	// if nil Tr.Dial will be used
    30  	ConnectDial func(network string, addr string) (net.Conn, error)
    31  }
    32  
    33  var hasPort = regexp.MustCompile(`:\d+$`)
    34  
    35  func copyHeaders(dst, src http.Header) {
    36  	for k := range dst {
    37  		dst.Del(k)
    38  	}
    39  	for k, vs := range src {
    40  		for _, v := range vs {
    41  			dst.Add(k, v)
    42  		}
    43  	}
    44  }
    45  
    46  func isEof(r *bufio.Reader) bool {
    47  	_, err := r.Peek(1)
    48  	if err == io.EOF {
    49  		return true
    50  	}
    51  	return false
    52  }
    53  
    54  func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) {
    55  	req = r
    56  	for _, h := range proxy.reqHandlers {
    57  		req, resp = h.Handle(r, ctx)
    58  		// non-nil resp means the handler decided to skip sending the request
    59  		// and return canned response instead.
    60  		if resp != nil {
    61  			break
    62  		}
    63  	}
    64  	return
    65  }
    66  func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) {
    67  	resp = respOrig
    68  	for _, h := range proxy.respHandlers {
    69  		ctx.Resp = resp
    70  		resp = h.Handle(resp, ctx)
    71  	}
    72  	return
    73  }
    74  
    75  func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) {
    76  	r.RequestURI = "" // this must be reset when serving a request with the client
    77  	ctx.Logf("Sending request %v %v", r.Method, r.URL.String())
    78  	// If no Accept-Encoding header exists, Transport will add the headers it can accept
    79  	// and would wrap the response body with the relevant reader.
    80  	r.Header.Del("Accept-Encoding")
    81  	// curl can add that, see
    82  	// http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-proxy-connection-header.html
    83  	r.Header.Del("Proxy-Connection")
    84  	r.Header.Del("Proxy-Authenticate")
    85  	r.Header.Del("Proxy-Authorization")
    86  	// Connection, Authenticate and Authorization are single hop Header:
    87  	// http://www.w3.org/Protocols/rfc2616/rfc2616.txt
    88  	// 14.10 Connection
    89  	//   The Connection general-header field allows the sender to specify
    90  	//   options that are desired for that particular connection and MUST NOT
    91  	//   be communicated by proxies over further connections.
    92  	r.Header.Del("Connection")
    93  }
    94  
    95  // Standard net/http function. Shouldn't be used directly, http.Serve will use it.
    96  func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    97  	//r.Header["X-Forwarded-For"] = w.RemoteAddr()
    98  	if r.Method == "CONNECT" {
    99  		proxy.handleHttps(w, r)
   100  	} else {
   101  		ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
   102  
   103  		var err error
   104  		ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String())
   105  
   106  		if (proxy.isReverseProxy && r.URL.IsAbs()) || (!proxy.isReverseProxy && !r.URL.IsAbs()) {
   107  			proxy.NonproxyHandler.ServeHTTP(w, r)
   108  			return
   109  		}
   110  		r, resp := proxy.filterRequest(r, ctx)
   111  
   112  		if resp == nil {
   113  			if proxy.isReverseProxy && (r.URL.Scheme == "" || r.URL.Host == "") {
   114  				panic("ReverseProxy did not rewrite request's Scheme or Host")
   115  			}
   116  
   117  			if isWebSocketRequest(r) {
   118  				ctx.Logf("Request looks like websocket upgrade.")
   119  				proxy.serveWebsocket(ctx, w, r)
   120  			}
   121  
   122  			removeProxyHeaders(ctx, r)
   123  			resp, err = ctx.RoundTrip(r)
   124  			if err != nil {
   125  				ctx.Error = err
   126  				resp = proxy.filterResponse(nil, ctx)
   127  				if resp == nil {
   128  					ctx.Logf("Error read response %v %v:", r.URL.Host, err.Error())
   129  					http.Error(w, err.Error(), 500)
   130  					return
   131  				}
   132  			}
   133  			ctx.Logf("Received response %v", resp.Status)
   134  		}
   135  		origBody := resp.Body
   136  		resp = proxy.filterResponse(resp, ctx)
   137  
   138  		ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode)
   139  		// http.ResponseWriter will take care of filling the correct response length
   140  		// Setting it now, might impose wrong value, contradicting the actual new
   141  		// body the user returned.
   142  		// We keep the original body to remove the header only if things changed.
   143  		// This will prevent problems with HEAD requests where there's no body, yet,
   144  		// the Content-Length header should be set.
   145  		if origBody != resp.Body {
   146  			resp.Header.Del("Content-Length")
   147  		}
   148  		copyHeaders(w.Header(), resp.Header)
   149  		w.WriteHeader(resp.StatusCode)
   150  		nr, err := io.Copy(w, resp.Body)
   151  		if err := resp.Body.Close(); err != nil {
   152  			ctx.Warnf("Can't close response body %v", err)
   153  		}
   154  		ctx.Logf("Copied %v bytes to client error=%v", nr, err)
   155  	}
   156  }
   157  
   158  // New proxy server, logs to StdErr by default
   159  func NewProxyHttpServer() *ProxyHttpServer {
   160  	proxy := ProxyHttpServer{
   161  		Logger:        log.New(os.Stderr, "", log.LstdFlags),
   162  		reqHandlers:   []ReqHandler{},
   163  		respHandlers:  []RespHandler{},
   164  		httpsHandlers: []HttpsHandler{},
   165  		NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   166  			http.Error(w, "This is a forward proxy server. Does not respond to non-proxy requests.", 500)
   167  		}),
   168  		Tr: &http.Transport{
   169  			TLSClientConfig: tlsClientSkipVerify,
   170  			Proxy:           http.ProxyFromEnvironment,
   171  		},
   172  	}
   173  
   174  	proxy.ConnectDial = dialerFromEnv(&proxy)
   175  
   176  	return &proxy
   177  }
   178  
   179  func NewReverseProxyHttpServer() *ProxyHttpServer {
   180  	reverseProxy := NewProxyHttpServer()
   181  
   182  	reverseProxy.isReverseProxy = true
   183  	reverseProxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   184  		http.Error(w, "This is a reverse proxy server. Does not respond to proxy requests.", 500)
   185  	})
   186  
   187  	return reverseProxy
   188  }