mirror of https://github.com/k3s-io/k3s
163 lines
4.9 KiB
Go
163 lines
4.9 KiB
Go
package goproxy
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// The basic proxy type. Implements http.Handler.
|
|
type ProxyHttpServer struct {
|
|
// session variable must be aligned in i386
|
|
// see http://golang.org/src/pkg/sync/atomic/doc.go#L41
|
|
sess int64
|
|
// setting Verbose to true will log information on each request sent to the proxy
|
|
Verbose bool
|
|
Logger *log.Logger
|
|
NonproxyHandler http.Handler
|
|
reqHandlers []ReqHandler
|
|
respHandlers []RespHandler
|
|
httpsHandlers []HttpsHandler
|
|
Tr *http.Transport
|
|
// ConnectDial will be used to create TCP connections for CONNECT requests
|
|
// if nil Tr.Dial will be used
|
|
ConnectDial func(network string, addr string) (net.Conn, error)
|
|
}
|
|
|
|
var hasPort = regexp.MustCompile(`:\d+$`)
|
|
|
|
func copyHeaders(dst, src http.Header) {
|
|
for k, _ := range dst {
|
|
dst.Del(k)
|
|
}
|
|
for k, vs := range src {
|
|
for _, v := range vs {
|
|
dst.Add(k, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func isEof(r *bufio.Reader) bool {
|
|
_, err := r.Peek(1)
|
|
if err == io.EOF {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) {
|
|
req = r
|
|
for _, h := range proxy.reqHandlers {
|
|
req, resp = h.Handle(r, ctx)
|
|
// non-nil resp means the handler decided to skip sending the request
|
|
// and return canned response instead.
|
|
if resp != nil {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) {
|
|
resp = respOrig
|
|
for _, h := range proxy.respHandlers {
|
|
ctx.Resp = resp
|
|
resp = h.Handle(resp, ctx)
|
|
}
|
|
return
|
|
}
|
|
|
|
func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) {
|
|
r.RequestURI = "" // this must be reset when serving a request with the client
|
|
ctx.Logf("Sending request %v %v", r.Method, r.URL.String())
|
|
// If no Accept-Encoding header exists, Transport will add the headers it can accept
|
|
// and would wrap the response body with the relevant reader.
|
|
r.Header.Del("Accept-Encoding")
|
|
// curl can add that, see
|
|
// https://jdebp.eu./FGA/web-proxy-connection-header.html
|
|
r.Header.Del("Proxy-Connection")
|
|
r.Header.Del("Proxy-Authenticate")
|
|
r.Header.Del("Proxy-Authorization")
|
|
// Connection, Authenticate and Authorization are single hop Header:
|
|
// http://www.w3.org/Protocols/rfc2616/rfc2616.txt
|
|
// 14.10 Connection
|
|
// The Connection general-header field allows the sender to specify
|
|
// options that are desired for that particular connection and MUST NOT
|
|
// be communicated by proxies over further connections.
|
|
r.Header.Del("Connection")
|
|
}
|
|
|
|
// Standard net/http function. Shouldn't be used directly, http.Serve will use it.
|
|
func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
//r.Header["X-Forwarded-For"] = w.RemoteAddr()
|
|
if r.Method == "CONNECT" {
|
|
proxy.handleHttps(w, r)
|
|
} else {
|
|
ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
|
|
|
|
var err error
|
|
ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String())
|
|
if !r.URL.IsAbs() {
|
|
proxy.NonproxyHandler.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
r, resp := proxy.filterRequest(r, ctx)
|
|
|
|
if resp == nil {
|
|
removeProxyHeaders(ctx, r)
|
|
resp, err = ctx.RoundTrip(r)
|
|
if err != nil {
|
|
ctx.Error = err
|
|
resp = proxy.filterResponse(nil, ctx)
|
|
if resp == nil {
|
|
ctx.Logf("error read response %v %v:", r.URL.Host, err.Error())
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
}
|
|
ctx.Logf("Received response %v", resp.Status)
|
|
}
|
|
origBody := resp.Body
|
|
resp = proxy.filterResponse(resp, ctx)
|
|
defer origBody.Close()
|
|
ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode)
|
|
// http.ResponseWriter will take care of filling the correct response length
|
|
// Setting it now, might impose wrong value, contradicting the actual new
|
|
// body the user returned.
|
|
// We keep the original body to remove the header only if things changed.
|
|
// This will prevent problems with HEAD requests where there's no body, yet,
|
|
// the Content-Length header should be set.
|
|
if origBody != resp.Body {
|
|
resp.Header.Del("Content-Length")
|
|
}
|
|
copyHeaders(w.Header(), resp.Header)
|
|
w.WriteHeader(resp.StatusCode)
|
|
nr, err := io.Copy(w, resp.Body)
|
|
if err := resp.Body.Close(); err != nil {
|
|
ctx.Warnf("Can't close response body %v", err)
|
|
}
|
|
ctx.Logf("Copied %v bytes to client error=%v", nr, err)
|
|
}
|
|
}
|
|
|
|
// New proxy server, logs to StdErr by default
|
|
func NewProxyHttpServer() *ProxyHttpServer {
|
|
proxy := ProxyHttpServer{
|
|
Logger: log.New(os.Stderr, "", log.LstdFlags),
|
|
reqHandlers: []ReqHandler{},
|
|
respHandlers: []RespHandler{},
|
|
httpsHandlers: []HttpsHandler{},
|
|
NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", 500)
|
|
}),
|
|
Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify,
|
|
Proxy: http.ProxyFromEnvironment},
|
|
}
|
|
proxy.ConnectDial = dialerFromEnv(&proxy)
|
|
return &proxy
|
|
}
|