|
1 | | -# go-httpproxy |
| 1 | +# A Go HTTP proxy library which has KISS principle |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +`github.com/go-httpproxy/httpproxy` repository provides an HTTP proxy library |
| 6 | +for Go (golang). |
| 7 | + |
| 8 | +The library is regular HTTP proxy; supports HTTP, HTTPS through CONNECT. And |
| 9 | +also provides HTTPS connection using "Man in the Middle" style attack. |
| 10 | + |
| 11 | +It's easy to use. `httpproxy.Proxy` implements `Handler` interface of `net/http` |
| 12 | +package to offer `http.ListenAndServe` function. |
| 13 | + |
| 14 | +### Keep it simple, stupid! |
| 15 | + |
| 16 | +> KISS is an acronym for "Keep it simple, stupid" as a design principle. The |
| 17 | +KISS principle states that most systems work best if they are kept simple rather |
| 18 | +than made complicated; therefore simplicity should be a key goal in design and |
| 19 | +unnecessary complexity should be avoided. [Wikipedia] |
| 20 | + |
| 21 | +## Usage |
| 22 | + |
| 23 | +Library has two significant structs: Proxy and Context. |
| 24 | + |
| 25 | +### Proxy struct |
| 26 | + |
| 27 | +```go |
| 28 | +// Proxy defines parameters for running an HTTP Proxy. It implements |
| 29 | +// http.Handler interface for ListenAndServe function. If you need, you must |
| 30 | +// fill Proxy struct before handling requests. |
| 31 | +type Proxy struct { |
| 32 | + // Session number of last proxy request. |
| 33 | + SessionNo int64 |
| 34 | + |
| 35 | + // RoundTripper interface to obtain remote response. |
| 36 | + // By default, it uses &http.Transport{}. |
| 37 | + Rt http.RoundTripper |
| 38 | + |
| 39 | + // Certificate key pair. |
| 40 | + Ca tls.Certificate |
| 41 | + |
| 42 | + // User data to use free. |
| 43 | + UserData interface{} |
| 44 | + |
| 45 | + // Error handler. |
| 46 | + OnError func(ctx *Context, when string, err *Error, opErr error) |
| 47 | + |
| 48 | + // Accept handler. It greets proxy request like ServeHTTP function of |
| 49 | + // http.Handler. |
| 50 | + // If it returns true, stops processing proxy request. |
| 51 | + OnAccept func(ctx *Context, w http.ResponseWriter, r *http.Request) bool |
| 52 | + |
| 53 | + // Auth handler. If you need authentication, set this handler. |
| 54 | + // If it returns true, authentication succeeded. |
| 55 | + OnAuth func(ctx *Context, user string, pass string) bool |
| 56 | + |
| 57 | + // Connect handler. It sets connect action and new host. |
| 58 | + // If len(newhost) > 0, host changes. |
| 59 | + OnConnect func(ctx *Context, host string) (ConnectAction ConnectAction, |
| 60 | + newHost string) |
| 61 | + |
| 62 | + // Request handler. It greets remote request. |
| 63 | + // If it returns non-nil response, stops processing remote request. |
| 64 | + OnRequest func(ctx *Context, req *http.Request) (resp *http.Response) |
| 65 | + |
| 66 | + // Response handler. It greets remote response. |
| 67 | + // Remote response sends after this handler. |
| 68 | + OnResponse func(ctx *Context, req *http.Request, resp *http.Response) |
| 69 | + |
| 70 | + // If ConnectAction is ConnectMitm, it sets chunked to Transfer-Encoding. |
| 71 | + // By default, it is true. |
| 72 | + MitmChunked bool |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +### Context struct |
| 77 | + |
| 78 | +```go |
| 79 | +// Context keeps context of each proxy request. |
| 80 | +type Context struct { |
| 81 | + // Pointer of Proxy struct handled this context. |
| 82 | + // It's using internally. Don't change in Context struct! |
| 83 | + Prx *Proxy |
| 84 | + |
| 85 | + // Session number of this context obtained from Proxy struct. |
| 86 | + SessionNo int64 |
| 87 | + |
| 88 | + // Sub session number of processing remote connection. |
| 89 | + SubSessionNo int64 |
| 90 | + |
| 91 | + // Action of after the CONNECT, if proxy request method is CONNECT. |
| 92 | + // It's using internally. Don't change in Context struct! |
| 93 | + ConnectAction ConnectAction |
| 94 | + |
| 95 | + // Proxy request, if proxy request method is CONNECT. |
| 96 | + // It's using internally. Don't change in Context struct! |
| 97 | + ConnectReq *http.Request |
| 98 | + |
| 99 | + // Remote host, if proxy request method is CONNECT. |
| 100 | + // It's using internally. Don't change in Context struct! |
| 101 | + ConnectHost string |
| 102 | + |
| 103 | + // User data to use free. |
| 104 | + UserData interface{} |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +### Demo code |
| 109 | + |
| 110 | +```go |
| 111 | +package main |
| 112 | + |
| 113 | +import ( |
| 114 | + "log" |
| 115 | + "net/http" |
| 116 | + |
| 117 | + "github.com/go-httpproxy/httpproxy" |
| 118 | +) |
| 119 | + |
| 120 | +func OnError(ctx *httpproxy.Context, when string, |
| 121 | + err *httpproxy.Error, opErr error) { |
| 122 | + // Log errors. |
| 123 | + log.Printf("ERR: %s: %s [%s]", when, err, opErr) |
| 124 | +} |
| 125 | + |
| 126 | +func OnAccept(ctx *httpproxy.Context, w http.ResponseWriter, |
| 127 | + r *http.Request) bool { |
| 128 | + // Handle local request has path "/info" |
| 129 | + if r.Method == "GET" && !r.URL.IsAbs() && r.URL.Path == "/info" { |
| 130 | + w.Write([]byte("This is go-httpproxy.")) |
| 131 | + return true |
| 132 | + } |
| 133 | + return false |
| 134 | +} |
| 135 | + |
| 136 | +func OnAuth(ctx *httpproxy.Context, user string, pass string) bool { |
| 137 | + // Auth test user. |
| 138 | + if user == "test" && pass == "1234" { |
| 139 | + return true |
| 140 | + } |
| 141 | + return false |
| 142 | +} |
| 143 | + |
| 144 | +func OnConnect(ctx *httpproxy.Context, host string) ( |
| 145 | + ConnectAction httpproxy.ConnectAction, newHost string) { |
| 146 | + // Apply "Man in the Middle" to all ssl connections. Never change host. |
| 147 | + return httpproxy.ConnectMitm, host |
| 148 | +} |
| 149 | + |
| 150 | +func OnRequest(ctx *httpproxy.Context, req *http.Request) ( |
| 151 | + resp *http.Response) { |
| 152 | + // Log proxying requests. |
| 153 | + log.Printf("INFO: Proxy: %s %s", req.Method, req.URL.String()) |
| 154 | + return |
| 155 | +} |
| 156 | + |
| 157 | +func OnResponse(ctx *httpproxy.Context, req *http.Request, |
| 158 | + resp *http.Response) { |
| 159 | + // Add header "Via: go-httpproxy". |
| 160 | + resp.Header.Add("Via", "go-httpproxy") |
| 161 | +} |
| 162 | + |
| 163 | +func main() { |
| 164 | + // Create a new proxy with default certificate pair. |
| 165 | + prx, _ := httpproxy.NewProxy() |
| 166 | + |
| 167 | + // Set handlers. |
| 168 | + prx.OnError = OnError |
| 169 | + prx.OnAccept = OnAccept |
| 170 | + prx.OnAuth = OnAuth |
| 171 | + prx.OnConnect = OnConnect |
| 172 | + prx.OnRequest = OnRequest |
| 173 | + prx.OnResponse = OnResponse |
| 174 | + |
| 175 | + // Listen... |
| 176 | + http.ListenAndServe(":8080", prx) |
| 177 | +} |
| 178 | +``` |
| 179 | + |
| 180 | +## GoDoc |
| 181 | + |
| 182 | +[https://godoc.org/github.com/go-httpproxy/httpproxy](https://godoc.org/github.com/go-httpproxy/httpproxy) |
| 183 | + |
| 184 | +## To-Do |
| 185 | + |
| 186 | +* GoDoc |
0 commit comments