capture/main.go

284 lines
8.1 KiB
Go
Raw Normal View History

2017-11-08 00:10:54 +01:00
package main
import (
"bytes"
"compress/gzip"
2018-07-21 19:50:53 +02:00
"encoding/json"
2017-11-18 13:23:36 +01:00
"fmt"
2018-11-25 18:10:10 +01:00
"html/template"
2017-11-08 00:10:54 +01:00
"io/ioutil"
"net/http"
2018-11-22 22:45:20 +01:00
"net/http/httptest"
2017-11-08 00:10:54 +01:00
"net/http/httputil"
2018-08-04 01:53:54 +02:00
"net/url"
2019-03-10 17:05:05 +01:00
"os"
2019-07-27 15:57:16 +02:00
"path"
2019-03-12 23:39:18 +01:00
"path/filepath"
2018-11-24 11:03:16 +01:00
"plugin"
2018-07-21 19:50:53 +02:00
"strings"
2019-03-23 23:13:16 +01:00
"time"
2017-11-08 00:10:54 +01:00
2018-11-24 18:51:33 +01:00
"github.com/ofabricio/curl"
2017-11-21 22:36:40 +01:00
)
// StatusInternalProxyError is any unknown proxy error
const StatusInternalProxyError = 999
2017-11-08 00:10:54 +01:00
func main() {
2018-09-16 16:21:36 +02:00
config := ReadConfig()
2017-11-08 00:10:54 +01:00
2019-06-21 01:44:42 +02:00
proxyURL := "http://localhost:" + config.ProxyPort
2018-11-22 22:45:20 +01:00
2019-06-21 01:25:57 +02:00
fmt.Printf("\nListening on %s", proxyURL)
fmt.Printf("\n %s%s\n\n", proxyURL, config.DashboardPath)
2018-11-22 22:45:20 +01:00
2019-06-21 01:25:57 +02:00
fmt.Println(http.ListenAndServe(":"+config.ProxyPort, NewCaptureHandler(config)))
}
2018-11-25 22:24:59 +01:00
2019-06-21 01:25:57 +02:00
func NewCaptureHandler(config Config) http.Handler {
2018-09-16 16:18:39 +02:00
2019-06-21 01:25:57 +02:00
srv := NewCaptureService(config.MaxCaptures)
2017-11-18 13:23:36 +01:00
2019-06-21 01:25:57 +02:00
handler := NewRecorderHandler(srv, NewPluginHandler(NewProxyHandler(config.TargetURL)))
2017-11-18 13:23:36 +01:00
2019-06-21 01:25:57 +02:00
router := http.NewServeMux()
router.HandleFunc(config.DashboardPath, NewDashboardHTMLHandler(config))
router.HandleFunc(config.DashboardConnPath, NewDashboardConnHandler(srv))
router.HandleFunc(config.DashboardInfoPath, NewDashboardInfoHandler(srv))
router.HandleFunc(config.DashboardClearPath, NewDashboardClearHandler(srv))
router.HandleFunc(config.DashboardRetryPath, NewDashboardRetryHandler(srv, handler))
router.HandleFunc("/", handler)
return router
2017-11-08 00:10:54 +01:00
}
2018-12-01 23:59:33 +01:00
// NewDashboardConnHandler opens an event stream connection with the dashboard
// so that it is notified everytime a new capture arrives
2019-06-21 01:09:58 +02:00
func NewDashboardConnHandler(srv *CaptureService) http.HandlerFunc {
2018-12-01 22:45:41 +01:00
return func(rw http.ResponseWriter, req *http.Request) {
2018-11-25 18:10:10 +01:00
if _, ok := rw.(http.Flusher); !ok {
fmt.Printf("streaming not supported at %s\n", req.URL)
http.Error(rw, "streaming not supported", http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "text/event-stream")
rw.Header().Set("Cache-Control", "no-cache")
for {
2019-06-21 01:09:58 +02:00
jsn, _ := json.Marshal(srv.DashboardItems())
2018-11-25 18:10:10 +01:00
fmt.Fprintf(rw, "event: captures\ndata: %s\n\n", jsn)
rw.(http.Flusher).Flush()
select {
2019-06-21 01:09:58 +02:00
case <-srv.Updated():
2018-11-25 18:10:10 +01:00
case <-req.Context().Done():
return
}
}
2018-12-01 22:45:41 +01:00
}
2017-11-21 22:36:40 +01:00
}
2018-12-01 23:59:33 +01:00
// NewDashboardClearHandler clears all the captures
2019-06-21 01:09:58 +02:00
func NewDashboardClearHandler(srv *CaptureService) http.HandlerFunc {
2018-12-01 22:45:41 +01:00
return func(rw http.ResponseWriter, req *http.Request) {
2019-06-21 01:09:58 +02:00
srv.RemoveAll()
2018-11-16 22:39:53 +01:00
rw.WriteHeader(http.StatusOK)
2018-12-01 22:45:41 +01:00
}
2018-09-07 16:45:02 +02:00
}
2018-12-01 23:59:33 +01:00
// NewDashboardHTMLHandler returns the dashboard html page
func NewDashboardHTMLHandler(config Config) http.HandlerFunc {
2018-12-01 22:45:41 +01:00
return func(rw http.ResponseWriter, req *http.Request) {
2018-11-16 22:39:53 +01:00
rw.Header().Add("Content-Type", "text/html")
2018-11-25 18:10:10 +01:00
t, err := template.New("dashboard template").Delims("<<", ">>").Parse(dashboardHTML)
if err != nil {
msg := fmt.Sprintf("could not parse dashboard html template: %v", err)
fmt.Println(msg)
http.Error(rw, msg, http.StatusInternalServerError)
return
}
t.Execute(rw, config)
2018-12-01 22:45:41 +01:00
}
2018-08-04 01:53:54 +02:00
}
2018-12-01 23:59:33 +01:00
// NewDashboardRetryHandler retries a request
2019-06-21 01:09:58 +02:00
func NewDashboardRetryHandler(srv *CaptureService, next http.HandlerFunc) http.HandlerFunc {
2018-12-01 22:45:41 +01:00
return func(rw http.ResponseWriter, req *http.Request) {
2019-07-27 15:57:16 +02:00
id := path.Base(req.URL.Path)
2019-06-21 01:09:58 +02:00
capture := srv.Find(id)
2019-06-21 02:06:50 +02:00
reqBody, _ := ioutil.ReadAll(capture.Req.Body)
req.Body.Close()
capture.Req.Body = ioutil.NopCloser(bytes.NewReader(reqBody))
// creates a new request based on the current one
2018-12-02 17:57:32 +01:00
r, _ := http.NewRequest(capture.Req.Method, capture.Req.URL.String(), bytes.NewReader(reqBody))
2018-11-25 22:24:59 +01:00
r.Header = capture.Req.Header
2019-06-21 02:06:50 +02:00
2018-11-25 22:24:59 +01:00
next.ServeHTTP(rw, r)
2018-12-01 22:45:41 +01:00
}
2018-11-25 22:24:59 +01:00
}
2019-03-24 14:46:02 +01:00
// NewDashboardInfoHandler returns the full capture info
2019-06-21 01:09:58 +02:00
func NewDashboardInfoHandler(srv *CaptureService) http.HandlerFunc {
2018-12-01 22:45:41 +01:00
return func(rw http.ResponseWriter, req *http.Request) {
2019-07-27 15:57:16 +02:00
id := path.Base(req.URL.Path)
2019-06-21 01:09:58 +02:00
capture := srv.Find(id)
2018-11-22 22:45:20 +01:00
rw.Header().Add("Content-Type", "application/json")
2018-11-24 15:24:16 +01:00
json.NewEncoder(rw).Encode(dump(capture))
2018-12-01 22:45:41 +01:00
}
2018-08-02 00:36:23 +02:00
}
2019-06-21 01:09:58 +02:00
// NewPluginHandler loads plugin files in the current directory. They are loaded sorted by filename.
func NewPluginHandler(next http.HandlerFunc) http.HandlerFunc {
2019-03-12 23:39:18 +01:00
ex, err := os.Executable()
if err != nil {
fmt.Println("error: could not get executable:", err)
return next
}
exPath := filepath.Dir(ex)
files, err := ioutil.ReadDir(exPath)
2018-11-24 11:03:16 +01:00
if err != nil {
2019-03-10 16:15:15 +01:00
fmt.Println("error: could not read directory:", err)
2018-11-24 11:03:16 +01:00
return next
}
2019-03-10 16:15:15 +01:00
for _, file := range files {
if file.IsDir() {
continue
}
if strings.HasSuffix(file.Name(), ".so") {
2019-06-21 01:44:42 +02:00
fmt.Printf("Loading plugin '%s'\n", file.Name())
2019-03-12 23:39:18 +01:00
p, err := plugin.Open(exPath + "/" + file.Name())
2019-03-10 16:15:15 +01:00
if err != nil {
fmt.Println("error: could not open plugin:", err)
2019-03-10 17:05:05 +01:00
os.Exit(1)
2019-03-10 16:15:15 +01:00
}
fn, err := p.Lookup("Handler")
if err != nil {
fmt.Println("error: could not find plugin Handler function:", err)
2019-03-10 17:05:05 +01:00
os.Exit(1)
2019-03-10 16:15:15 +01:00
}
pluginHandler, ok := fn.(func(http.HandlerFunc) http.HandlerFunc)
if !ok {
fmt.Println("error: plugin Handler function should be 'func(http.HandlerFunc) http.HandlerFunc'")
2019-03-10 17:05:05 +01:00
os.Exit(1)
2019-03-10 16:15:15 +01:00
}
next = pluginHandler(next)
}
2018-11-24 11:03:16 +01:00
}
2019-03-10 16:15:15 +01:00
return next
2018-11-24 11:03:16 +01:00
}
2019-06-21 01:44:42 +02:00
// NewRecorderHandler records all the traffic data
2019-06-21 01:09:58 +02:00
func NewRecorderHandler(srv *CaptureService, next http.HandlerFunc) http.HandlerFunc {
2018-12-01 22:45:41 +01:00
return func(rw http.ResponseWriter, req *http.Request) {
2018-11-24 15:24:16 +01:00
// save req body for later
2019-06-21 02:06:50 +02:00
reqBody, _ := ioutil.ReadAll(req.Body)
req.Body.Close()
req.Body = ioutil.NopCloser(bytes.NewReader(reqBody))
2017-11-08 00:10:54 +01:00
2018-11-22 22:45:20 +01:00
rec := httptest.NewRecorder()
2019-03-23 23:13:16 +01:00
start := time.Now()
2018-11-22 22:45:20 +01:00
next.ServeHTTP(rec, req)
2019-03-23 23:13:16 +01:00
elapsed := time.Since(start).Truncate(time.Millisecond) / time.Millisecond
2018-11-24 15:24:16 +01:00
// respond
2019-03-10 16:46:38 +01:00
for k, v := range rec.Header() {
2018-11-22 22:45:20 +01:00
rw.Header()[k] = v
}
rw.WriteHeader(rec.Code)
rw.Write(rec.Body.Bytes())
2018-11-24 15:24:16 +01:00
// record req and res
req.Body = ioutil.NopCloser(bytes.NewReader(reqBody))
2018-11-22 22:45:20 +01:00
res := rec.Result()
2019-06-21 01:09:58 +02:00
srv.Insert(Capture{Req: req, Res: res, Elapsed: elapsed})
2018-12-01 22:45:41 +01:00
}
2018-11-22 22:45:20 +01:00
}
2018-12-01 23:59:33 +01:00
// NewProxyHandler is the reverse proxy handler
2018-12-01 22:45:41 +01:00
func NewProxyHandler(URL string) http.HandlerFunc {
2018-11-22 22:45:20 +01:00
url, _ := url.Parse(URL)
proxy := httputil.NewSingleHostReverseProxy(url)
proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
2019-06-21 01:44:42 +02:00
fmt.Printf("Uh oh | %v | %s %s\n", err, req.Method, req.URL)
rw.WriteHeader(StatusInternalProxyError)
fmt.Fprintf(rw, "%v", err)
2018-11-22 22:45:20 +01:00
}
2018-12-01 22:45:41 +01:00
return func(rw http.ResponseWriter, req *http.Request) {
2018-11-22 22:45:20 +01:00
req.Host = url.Host
req.URL.Host = url.Host
req.URL.Scheme = url.Scheme
2018-11-11 17:54:35 +01:00
proxy.ServeHTTP(rw, req)
2018-12-01 22:45:41 +01:00
}
2018-08-04 01:53:54 +02:00
}
2018-11-24 15:24:16 +01:00
func dump(c *Capture) CaptureDump {
reqDump, err := dumpRequest(c.Req)
if err != nil {
fmt.Printf("could not dump request: %v\n", err)
}
resDump, err := dumpResponse(c.Res)
if err != nil {
fmt.Printf("could not dump response: %v\n", err)
}
2018-11-24 18:51:33 +01:00
strcurl, err := curl.New(c.Req)
if err != nil {
fmt.Printf("could not convert request to curl: %v\n", err)
}
return CaptureDump{Request: string(reqDump), Response: string(resDump), Curl: strcurl}
2018-11-24 15:24:16 +01:00
}
2018-08-04 01:53:54 +02:00
func dumpRequest(req *http.Request) ([]byte, error) {
2018-11-11 17:54:35 +01:00
if req.Header.Get("Content-Encoding") == "gzip" {
2018-12-01 23:59:33 +01:00
return dumpGzipRequest(req)
2018-11-11 17:54:35 +01:00
}
2018-07-21 19:50:53 +02:00
return httputil.DumpRequest(req, true)
}
2018-12-01 23:59:33 +01:00
func dumpGzipRequest(req *http.Request) ([]byte, error) {
2019-06-21 02:06:50 +02:00
reqBody, _ := ioutil.ReadAll(req.Body)
req.Body.Close()
req.Body = ioutil.NopCloser(bytes.NewReader(reqBody))
2018-12-01 23:59:33 +01:00
reader, _ := gzip.NewReader(bytes.NewReader(reqBody))
req.Body = ioutil.NopCloser(reader)
reqDump, err := httputil.DumpRequest(req, true)
req.Body = ioutil.NopCloser(bytes.NewReader(reqBody))
return reqDump, err
}
2018-08-04 01:53:54 +02:00
func dumpResponse(res *http.Response) ([]byte, error) {
if res.StatusCode == StatusInternalProxyError {
2019-06-21 02:06:50 +02:00
return dumpResponseBody(res)
}
if res.Header.Get("Content-Encoding") == "gzip" {
2018-12-01 23:59:33 +01:00
return dumpGzipResponse(res)
}
2018-11-11 17:54:35 +01:00
return httputil.DumpResponse(res, true)
}
2018-09-07 16:45:02 +02:00
2018-12-01 23:59:33 +01:00
// Dumps only the body when we have an proxy error.
// This body is set in NewProxyHandler() in proxy.ErrorHandler
2019-06-21 02:06:50 +02:00
func dumpResponseBody(res *http.Response) ([]byte, error) {
resBody, _ := ioutil.ReadAll(res.Body)
res.Body.Close()
res.Body = ioutil.NopCloser(bytes.NewReader(resBody))
2018-12-01 23:59:33 +01:00
return resBody, nil
}
func dumpGzipResponse(res *http.Response) ([]byte, error) {
2019-06-21 02:06:50 +02:00
resBody, _ := ioutil.ReadAll(res.Body)
res.Body.Close()
res.Body = ioutil.NopCloser(bytes.NewReader(resBody))
2018-12-01 23:59:33 +01:00
reader, _ := gzip.NewReader(bytes.NewReader(resBody))
res.Body = ioutil.NopCloser(reader)
resDump, err := httputil.DumpResponse(res, true)
res.Body = ioutil.NopCloser(bytes.NewReader(resBody))
return resDump, err
}