Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use a store for Handlers #527

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ There are 3 kinds of useful handlers to manipulate the behavior, as follows:
```go
// handler called after receiving HTTP CONNECT from the client, and before proxy establish connection
// with destination host
httpsHandlers []HttpsHandler
HttpsHandlers []HttpsHandler

// handler called before proxy send HTTP request to destination host
reqHandlers []ReqHandler
ReqHandlers []ReqHandler

// handler called after proxy receives HTTP Response from destination host, and before proxy forward
// the Response to the client.
respHandlers []RespHandler
RespHandlers []RespHandler
```

Depending on what you want to manipulate, the ways to add handlers to each handler list are:
Expand Down
15 changes: 11 additions & 4 deletions dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func StatusCodeIs(codes ...int) RespCondition {
// You will use the ReqProxyConds struct to register a ReqHandler, that would filter
// the request, only if all the given ReqCondition matched.
// Typical usage:
//
// proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...)
func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds {
return &ReqProxyConds{proxy, conds}
Expand All @@ -201,12 +202,13 @@ func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*ht
// ReqProxyConds.Do will register the ReqHandler on the proxy,
// the ReqHandler will handle the HTTP request if all the conditions
// aggregated in the ReqProxyConds are met. Typical usage:
//
// proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy
// proxy.OnRequest(cond1,cond2).Do(handler)
// // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true
// // if they are, will call handler.Handle(req,ctx)
func (pcond *ReqProxyConds) Do(h ReqHandler) {
pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers,
pcond.proxy.HandlerStore.ReqHandlers = append(pcond.proxy.HandlerStore.ReqHandlers,
FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) {
for _, cond := range pcond.reqConds {
if !cond.HandleReq(r, ctx) {
Expand All @@ -227,9 +229,10 @@ func (pcond *ReqProxyConds) Do(h ReqHandler) {
// connection.
// The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy
// will use the default tls configuration.
//
// proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests
func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) {
pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers,
pcond.proxy.HandlerStore.HttpsHandlers = append(pcond.proxy.HandlerStore.HttpsHandlers,
FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
for _, cond := range pcond.reqConds {
if !cond.HandleReq(ctx.Req, ctx) {
Expand All @@ -242,6 +245,7 @@ func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) {

// HandleConnectFunc is equivalent to HandleConnect,
// for example, accepting CONNECT request if they contain a password in header
//
// io.WriteString(h,password)
// passHash := h.Sum(nil)
// proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
Expand All @@ -257,7 +261,7 @@ func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx)
}

func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) {
pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers,
pcond.proxy.HandlerStore.HttpsHandlers = append(pcond.proxy.HandlerStore.HttpsHandlers,
FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
for _, cond := range pcond.reqConds {
if !cond.HandleReq(ctx.Req, ctx) {
Expand Down Expand Up @@ -285,7 +289,7 @@ func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http
// ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every
// request that matches the conditions aggregated in pcond.
func (pcond *ProxyConds) Do(h RespHandler) {
pcond.proxy.respHandlers = append(pcond.proxy.respHandlers,
pcond.proxy.HandlerStore.RespHandlers = append(pcond.proxy.HandlerStore.RespHandlers,
FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response {
for _, cond := range pcond.reqConds {
if !cond.HandleReq(ctx.Req, ctx) {
Expand All @@ -302,6 +306,7 @@ func (pcond *ProxyConds) Do(h RespHandler) {
}

// OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is
//
// proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used
// // if cond1.HandleResp(resp) && cond2.HandleResp(resp)
func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds {
Expand All @@ -310,13 +315,15 @@ func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds {

// AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to
// eavesdrop all https connections to www.google.com, we can use
//
// proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm)
var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
return MitmConnect, host
}

// AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow
// connections to hosts on any other port than 443
//
// proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))).
// HandleConnect(goproxy.AlwaysReject)
var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
Expand Down
4 changes: 2 additions & 2 deletions https.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
panic("Cannot hijack connection " + e.Error())
}

ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers))
ctx.Logf("Running %d CONNECT handlers", len(proxy.HandlerStore.HttpsHandlers))
todo, host := OkConnect, r.URL.Host
for i, h := range proxy.httpsHandlers {
for i, h := range proxy.HandlerStore.HttpsHandlers {
newtodo, newhost := h.HandleConnect(host, ctx)

// If found a result, break the loop immediately
Expand Down
29 changes: 20 additions & 9 deletions proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ type ProxyHttpServer struct {
Verbose bool
Logger Logger
NonproxyHandler http.Handler
reqHandlers []ReqHandler
respHandlers []RespHandler
httpsHandlers []HttpsHandler
HandlerStore *HandlersStore
Tr *http.Transport
// ConnectDial will be used to create TCP connections for CONNECT requests
// if nil Tr.Dial will be used
Expand All @@ -33,6 +31,21 @@ type ProxyHttpServer struct {
KeepHeader bool
}

type HandlersStore struct {
ReqHandlers []ReqHandler
RespHandlers []RespHandler
HttpsHandlers []HttpsHandler
}

func NewHandlersStore() *HandlersStore {
return &HandlersStore{
ReqHandlers: make([]ReqHandler, 0),
RespHandlers: make([]RespHandler, 0),
HttpsHandlers: make([]HttpsHandler, 0),
}
}

// handler interface using generics
var hasPort = regexp.MustCompile(`:\d+$`)

func copyHeaders(dst, src http.Header, keepDestHeaders bool) {
Expand All @@ -58,7 +71,7 @@ func isEof(r *bufio.Reader) bool {

func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) {
req = r
for _, h := range proxy.reqHandlers {
for _, h := range proxy.HandlerStore.ReqHandlers {
req, resp = h.Handle(r, ctx)
// non-nil resp means the handler decided to skip sending the request
// and return canned response instead.
Expand All @@ -70,7 +83,7 @@ func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req
}
func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) {
resp = respOrig
for _, h := range proxy.respHandlers {
for _, h := range proxy.HandlerStore.RespHandlers {
ctx.Resp = resp
resp = h.Handle(resp, ctx)
}
Expand Down Expand Up @@ -188,10 +201,8 @@ func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
// NewProxyHttpServer creates and returns a proxy server, logging to stderr by default
func NewProxyHttpServer() *ProxyHttpServer {
proxy := ProxyHttpServer{
Logger: log.New(os.Stderr, "", log.LstdFlags),
reqHandlers: []ReqHandler{},
respHandlers: []RespHandler{},
httpsHandlers: []HttpsHandler{},
Logger: log.New(os.Stderr, "", log.LstdFlags),
HandlerStore: NewHandlersStore(),
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)
}),
Expand Down