2017-11-08 00:10:54 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2017-11-15 15:37:54 +01:00
|
|
|
"compress/gzip"
|
2018-07-21 19:50:53 +02:00
|
|
|
"encoding/json"
|
2017-11-18 13:23:36 +01:00
|
|
|
"fmt"
|
2017-11-08 00:10:54 +01:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httputil"
|
2018-08-04 01:53:54 +02:00
|
|
|
"net/url"
|
2018-07-21 19:50:53 +02:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2017-11-08 00:10:54 +01:00
|
|
|
|
2017-11-21 22:36:40 +01:00
|
|
|
"github.com/googollee/go-socket.io"
|
|
|
|
)
|
2017-11-15 15:37:54 +01:00
|
|
|
|
2018-08-02 00:36:23 +02:00
|
|
|
type transport struct {
|
2017-11-15 15:37:54 +01:00
|
|
|
http.RoundTripper
|
2018-08-04 01:53:54 +02:00
|
|
|
itemInfoPath string
|
|
|
|
maxItems int
|
|
|
|
currItemID int
|
2017-11-15 15:37:54 +01:00
|
|
|
}
|
|
|
|
|
2018-07-21 19:50:53 +02:00
|
|
|
var captures Captures
|
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
var dashboardSocket socketio.Socket
|
2018-08-02 11:09:00 +02:00
|
|
|
|
2017-11-08 00:10:54 +01:00
|
|
|
func main() {
|
2018-08-04 01:53:54 +02:00
|
|
|
args := ParseArgs()
|
2017-11-08 00:10:54 +01:00
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
proxyHost := fmt.Sprintf("http://localhost:%s", args.proxyPort)
|
|
|
|
dashboardPath := fmt.Sprintf("/%s/", args.dashboard)
|
|
|
|
dashboardItemInfoPath := fmt.Sprintf("/%s/items/", args.dashboard)
|
2018-07-21 19:50:53 +02:00
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
transp := &transport{
|
|
|
|
RoundTripper: http.DefaultTransport,
|
|
|
|
itemInfoPath: dashboardItemInfoPath,
|
|
|
|
maxItems: args.maxCaptures,
|
|
|
|
currItemID: 0,
|
|
|
|
}
|
2018-07-21 19:50:53 +02:00
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
http.Handle("/", getProxyHandler(args.targetURL, transp))
|
|
|
|
http.Handle("/socket.io/", getDashboardSocketHandler())
|
|
|
|
http.Handle(dashboardPath, getDashboardHandler())
|
|
|
|
http.Handle(dashboardItemInfoPath, getDashboardItemInfoHandler())
|
2017-11-18 13:23:36 +01:00
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
fmt.Printf("\nListening on %s", proxyHost)
|
|
|
|
fmt.Printf("\n %s/%s\n\n", proxyHost, args.dashboard)
|
2017-11-18 13:23:36 +01:00
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
fmt.Println(http.ListenAndServe(":"+args.proxyPort, nil))
|
2017-11-08 00:10:54 +01:00
|
|
|
}
|
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
func getDashboardSocketHandler() http.Handler {
|
2017-11-21 22:36:40 +01:00
|
|
|
server, err := socketio.NewServer(nil)
|
|
|
|
if err != nil {
|
2018-08-04 01:53:54 +02:00
|
|
|
fmt.Println("socket server error", err)
|
2017-11-21 22:36:40 +01:00
|
|
|
}
|
|
|
|
server.On("connection", func(so socketio.Socket) {
|
2018-08-04 01:53:54 +02:00
|
|
|
dashboardSocket = so
|
|
|
|
dashboardSocket.Emit("captures", captures.MetadataOnly())
|
2017-11-21 22:36:40 +01:00
|
|
|
})
|
|
|
|
server.On("error", func(so socketio.Socket, err error) {
|
2018-08-04 01:53:54 +02:00
|
|
|
fmt.Println("socket error", err)
|
2017-11-21 22:36:40 +01:00
|
|
|
})
|
|
|
|
return server
|
|
|
|
}
|
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
func getDashboardHandler() http.Handler {
|
2018-08-02 00:36:23 +02:00
|
|
|
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
2018-08-04 01:53:54 +02:00
|
|
|
res.Header().Add("Content-Type", "text/html")
|
|
|
|
res.Write([]byte(dashboardHTML))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDashboardItemInfoHandler() http.Handler {
|
|
|
|
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
|
|
id := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]
|
2018-08-02 00:36:23 +02:00
|
|
|
i, _ := strconv.Atoi(id)
|
|
|
|
json, _ := json.Marshal(captures[i])
|
|
|
|
res.Header().Add("Content-Type", "application/json")
|
|
|
|
res.Write([]byte(json))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
func getProxyHandler(url *url.URL, transp *transport) http.Handler {
|
|
|
|
proxy := httputil.NewSingleHostReverseProxy(url)
|
|
|
|
proxy.Transport = transp
|
|
|
|
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
|
|
request.Host = request.URL.Host
|
|
|
|
proxy.ServeHTTP(response, request)
|
2018-08-02 00:36:23 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
2017-11-08 00:10:54 +01:00
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
reqDump, err := dumpRequest(req)
|
2017-11-19 16:51:26 +01:00
|
|
|
if err != nil {
|
2018-07-21 19:50:53 +02:00
|
|
|
return nil, err
|
2017-11-19 16:51:26 +01:00
|
|
|
}
|
2017-11-08 00:10:54 +01:00
|
|
|
|
2018-07-22 20:14:27 +02:00
|
|
|
res, err := t.RoundTripper.RoundTrip(req)
|
|
|
|
if err != nil {
|
2018-08-02 11:09:00 +02:00
|
|
|
return nil, fmt.Errorf("uh oh | %v | %s", err, req.URL)
|
2018-07-22 20:14:27 +02:00
|
|
|
}
|
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
resDump, err := dumpResponse(res)
|
2017-11-19 16:51:26 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-11-15 15:37:54 +01:00
|
|
|
|
2018-07-21 19:50:53 +02:00
|
|
|
capture := Capture{
|
2018-08-04 01:53:54 +02:00
|
|
|
ID: t.NewItemID(),
|
2018-07-21 19:50:53 +02:00
|
|
|
Path: req.URL.Path,
|
|
|
|
Method: req.Method,
|
|
|
|
Status: res.StatusCode,
|
2018-08-04 01:53:54 +02:00
|
|
|
InfoPath: t.itemInfoPath,
|
2018-07-21 19:50:53 +02:00
|
|
|
Request: string(reqDump),
|
|
|
|
Response: string(resDump),
|
2017-11-15 15:37:54 +01:00
|
|
|
}
|
|
|
|
|
2017-11-21 22:36:40 +01:00
|
|
|
captures.Add(capture)
|
2018-08-04 01:53:54 +02:00
|
|
|
captures.RemoveLastAfterReaching(t.maxItems)
|
|
|
|
if dashboardSocket != nil {
|
|
|
|
dashboardSocket.Emit("captures", captures.MetadataOnly())
|
|
|
|
}
|
2017-11-19 16:51:26 +01:00
|
|
|
return res, nil
|
2017-11-08 00:10:54 +01:00
|
|
|
}
|
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
func (t *transport) NewItemID() int {
|
|
|
|
t.currItemID++
|
|
|
|
return t.currItemID
|
|
|
|
}
|
|
|
|
|
|
|
|
func dumpRequest(req *http.Request) ([]byte, error) {
|
2018-07-21 19:50:53 +02:00
|
|
|
return httputil.DumpRequest(req, true)
|
|
|
|
}
|
|
|
|
|
2018-08-04 01:53:54 +02:00
|
|
|
func dumpResponse(res *http.Response) ([]byte, error) {
|
2017-11-15 15:37:54 +01:00
|
|
|
var originalBody bytes.Buffer
|
2018-07-22 20:14:27 +02:00
|
|
|
reader := io.TeeReader(res.Body, &originalBody)
|
2017-11-15 15:37:54 +01:00
|
|
|
if res.Header.Get("Content-Encoding") == "gzip" {
|
2018-07-22 20:14:27 +02:00
|
|
|
reader, _ = gzip.NewReader(reader)
|
2017-11-15 15:37:54 +01:00
|
|
|
}
|
2018-07-22 20:14:27 +02:00
|
|
|
res.Body = ioutil.NopCloser(reader)
|
2017-11-18 12:42:53 +01:00
|
|
|
resDump, err := httputil.DumpResponse(res, true)
|
2017-11-15 15:37:54 +01:00
|
|
|
res.Body = ioutil.NopCloser(&originalBody)
|
2017-11-18 12:42:53 +01:00
|
|
|
return resDump, err
|
2017-11-15 15:37:54 +01:00
|
|
|
}
|