comment code
This commit is contained in:
parent
e48bc52d12
commit
b336d33dd3
11
capture.go
11
capture.go
|
@ -9,6 +9,7 @@ import (
|
||||||
var captureID int
|
var captureID int
|
||||||
var captures CaptureList
|
var captures CaptureList
|
||||||
|
|
||||||
|
// CaptureList stores all captures
|
||||||
type CaptureList struct {
|
type CaptureList struct {
|
||||||
items []Capture
|
items []Capture
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
|
@ -17,12 +18,14 @@ type CaptureList struct {
|
||||||
Updated chan struct{}
|
Updated chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capture saves our traffic data
|
||||||
type Capture struct {
|
type Capture struct {
|
||||||
ID int
|
ID int
|
||||||
Req *http.Request
|
Req *http.Request
|
||||||
Res *http.Response
|
Res *http.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaptureMetadata is the data for each list item in the dashboard
|
||||||
type CaptureMetadata struct {
|
type CaptureMetadata struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
|
@ -30,12 +33,14 @@ type CaptureMetadata struct {
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaptureDump saves all the dumps shown in the dashboard
|
||||||
type CaptureDump struct {
|
type CaptureDump struct {
|
||||||
Request string `json:"request"`
|
Request string `json:"request"`
|
||||||
Response string `json:"response"`
|
Response string `json:"response"`
|
||||||
Curl string `json:"curl"`
|
Curl string `json:"curl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metadata returns the metadada of a capture
|
||||||
func (c *Capture) Metadata() CaptureMetadata {
|
func (c *Capture) Metadata() CaptureMetadata {
|
||||||
return CaptureMetadata{
|
return CaptureMetadata{
|
||||||
ID: c.ID,
|
ID: c.ID,
|
||||||
|
@ -45,6 +50,7 @@ func (c *Capture) Metadata() CaptureMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCaptureList creates a new list of captures
|
||||||
func NewCaptureList(maxItems int) *CaptureList {
|
func NewCaptureList(maxItems int) *CaptureList {
|
||||||
return &CaptureList{
|
return &CaptureList{
|
||||||
maxItems: maxItems,
|
maxItems: maxItems,
|
||||||
|
@ -52,6 +58,7 @@ func NewCaptureList(maxItems int) *CaptureList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert adds a new capture
|
||||||
func (c *CaptureList) Insert(capture Capture) {
|
func (c *CaptureList) Insert(capture Capture) {
|
||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
@ -63,6 +70,7 @@ func (c *CaptureList) Insert(capture Capture) {
|
||||||
c.signalsItemsChange()
|
c.signalsItemsChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find finds a capture by its id
|
||||||
func (c *CaptureList) Find(captureID string) *Capture {
|
func (c *CaptureList) Find(captureID string) *Capture {
|
||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
@ -75,6 +83,7 @@ func (c *CaptureList) Find(captureID string) *Capture {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveAll removes all the captures
|
||||||
func (c *CaptureList) RemoveAll() {
|
func (c *CaptureList) RemoveAll() {
|
||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
@ -82,10 +91,12 @@ func (c *CaptureList) RemoveAll() {
|
||||||
c.signalsItemsChange()
|
c.signalsItemsChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Items returns all the captures
|
||||||
func (c *CaptureList) Items() []Capture {
|
func (c *CaptureList) Items() []Capture {
|
||||||
return c.items
|
return c.items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ItemsAsMetadata returns all the captures as metadata
|
||||||
func (c *CaptureList) ItemsAsMetadata() []CaptureMetadata {
|
func (c *CaptureList) ItemsAsMetadata() []CaptureMetadata {
|
||||||
c.mux.Lock()
|
c.mux.Lock()
|
||||||
defer c.mux.Unlock()
|
defer c.mux.Unlock()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Config has all the configuration parsed from the command line
|
||||||
type Config struct {
|
type Config struct {
|
||||||
TargetURL string `json:"targetURL"`
|
TargetURL string `json:"targetURL"`
|
||||||
ProxyPort string `json:"proxyPort"`
|
ProxyPort string `json:"proxyPort"`
|
||||||
|
@ -17,6 +18,7 @@ type Config struct {
|
||||||
DashboardItemInfoPath string `json:"dashboardItemInfoPath"`
|
DashboardItemInfoPath string `json:"dashboardItemInfoPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadConfig reads the arguments from the command line
|
||||||
func ReadConfig() Config {
|
func ReadConfig() Config {
|
||||||
targetURL := flag.String("url", "https://jsonplaceholder.typicode.com", "Required. Set the base url you want to capture")
|
targetURL := flag.String("url", "https://jsonplaceholder.typicode.com", "Required. Set the base url you want to capture")
|
||||||
proxyPort := flag.String("port", "9000", "Set the proxy port")
|
proxyPort := flag.String("port", "9000", "Set the proxy port")
|
||||||
|
|
63
main.go
63
main.go
|
@ -33,7 +33,7 @@ func startCapture(config Config) {
|
||||||
handler := NewPlugin(NewRecorder(list, NewProxyHandler(config.TargetURL)))
|
handler := NewPlugin(NewRecorder(list, NewProxyHandler(config.TargetURL)))
|
||||||
|
|
||||||
http.HandleFunc("/", handler)
|
http.HandleFunc("/", handler)
|
||||||
http.HandleFunc(config.DashboardPath, NewDashboardHtmlHandler(config))
|
http.HandleFunc(config.DashboardPath, NewDashboardHTMLHandler(config))
|
||||||
http.HandleFunc(config.DashboardConnPath, NewDashboardConnHandler(list))
|
http.HandleFunc(config.DashboardConnPath, NewDashboardConnHandler(list))
|
||||||
http.HandleFunc(config.DashboardClearPath, NewDashboardClearHandler(list))
|
http.HandleFunc(config.DashboardClearPath, NewDashboardClearHandler(list))
|
||||||
http.HandleFunc(config.DashboardRetryPath, NewDashboardRetryHandler(list, handler))
|
http.HandleFunc(config.DashboardRetryPath, NewDashboardRetryHandler(list, handler))
|
||||||
|
@ -47,6 +47,8 @@ func startCapture(config Config) {
|
||||||
fmt.Println(http.ListenAndServe(":"+config.ProxyPort, nil))
|
fmt.Println(http.ListenAndServe(":"+config.ProxyPort, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDashboardConnHandler opens an event stream connection with the dashboard
|
||||||
|
// so that it is notified everytime a new capture arrives
|
||||||
func NewDashboardConnHandler(list *CaptureList) http.HandlerFunc {
|
func NewDashboardConnHandler(list *CaptureList) http.HandlerFunc {
|
||||||
return func(rw http.ResponseWriter, req *http.Request) {
|
return func(rw http.ResponseWriter, req *http.Request) {
|
||||||
if _, ok := rw.(http.Flusher); !ok {
|
if _, ok := rw.(http.Flusher); !ok {
|
||||||
|
@ -74,6 +76,7 @@ func NewDashboardConnHandler(list *CaptureList) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDashboardClearHandler clears all the captures
|
||||||
func NewDashboardClearHandler(list *CaptureList) http.HandlerFunc {
|
func NewDashboardClearHandler(list *CaptureList) http.HandlerFunc {
|
||||||
return func(rw http.ResponseWriter, req *http.Request) {
|
return func(rw http.ResponseWriter, req *http.Request) {
|
||||||
list.RemoveAll()
|
list.RemoveAll()
|
||||||
|
@ -81,7 +84,8 @@ func NewDashboardClearHandler(list *CaptureList) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDashboardHtmlHandler(config Config) http.HandlerFunc {
|
// NewDashboardHTMLHandler returns the dashboard html page
|
||||||
|
func NewDashboardHTMLHandler(config Config) http.HandlerFunc {
|
||||||
return func(rw http.ResponseWriter, req *http.Request) {
|
return func(rw http.ResponseWriter, req *http.Request) {
|
||||||
rw.Header().Add("Content-Type", "text/html")
|
rw.Header().Add("Content-Type", "text/html")
|
||||||
t, err := template.New("dashboard template").Delims("<<", ">>").Parse(dashboardHTML)
|
t, err := template.New("dashboard template").Delims("<<", ">>").Parse(dashboardHTML)
|
||||||
|
@ -95,6 +99,7 @@ func NewDashboardHtmlHandler(config Config) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDashboardRetryHandler retries a request
|
||||||
func NewDashboardRetryHandler(list *CaptureList, next http.HandlerFunc) http.HandlerFunc {
|
func NewDashboardRetryHandler(list *CaptureList, next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(rw http.ResponseWriter, req *http.Request) {
|
return func(rw http.ResponseWriter, req *http.Request) {
|
||||||
id := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]
|
id := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]
|
||||||
|
@ -112,6 +117,7 @@ func NewDashboardRetryHandler(list *CaptureList, next http.HandlerFunc) http.Han
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDashboardItemInfoHandler returns the full capture info
|
||||||
func NewDashboardItemInfoHandler(list *CaptureList) http.HandlerFunc {
|
func NewDashboardItemInfoHandler(list *CaptureList) http.HandlerFunc {
|
||||||
return func(rw http.ResponseWriter, req *http.Request) {
|
return func(rw http.ResponseWriter, req *http.Request) {
|
||||||
id := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]
|
id := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]
|
||||||
|
@ -125,6 +131,7 @@ func NewDashboardItemInfoHandler(list *CaptureList) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPlugin setups plugin handler for requests and resposes
|
||||||
func NewPlugin(next http.HandlerFunc) http.HandlerFunc {
|
func NewPlugin(next http.HandlerFunc) http.HandlerFunc {
|
||||||
p, err := plugin.Open("plugin.so")
|
p, err := plugin.Open("plugin.so")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -146,6 +153,7 @@ func NewPlugin(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return pluginFn(next)
|
return pluginFn(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRecorder saves all the traffic data
|
||||||
func NewRecorder(list *CaptureList, next http.HandlerFunc) http.HandlerFunc {
|
func NewRecorder(list *CaptureList, next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(rw http.ResponseWriter, req *http.Request) {
|
return func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
@ -171,6 +179,7 @@ func NewRecorder(list *CaptureList, next http.HandlerFunc) http.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewProxyHandler is the reverse proxy handler
|
||||||
func NewProxyHandler(URL string) http.HandlerFunc {
|
func NewProxyHandler(URL string) http.HandlerFunc {
|
||||||
url, _ := url.Parse(URL)
|
url, _ := url.Parse(URL)
|
||||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||||
|
@ -205,37 +214,49 @@ func dump(c *Capture) CaptureDump {
|
||||||
|
|
||||||
func dumpRequest(req *http.Request) ([]byte, error) {
|
func dumpRequest(req *http.Request) ([]byte, error) {
|
||||||
if req.Header.Get("Content-Encoding") == "gzip" {
|
if req.Header.Get("Content-Encoding") == "gzip" {
|
||||||
var reqBody []byte
|
return dumpGzipRequest(req)
|
||||||
req.Body, reqBody = drain(req.Body)
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
return httputil.DumpRequest(req, true)
|
return httputil.DumpRequest(req, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dumpGzipRequest(req *http.Request) ([]byte, error) {
|
||||||
|
var reqBody []byte
|
||||||
|
req.Body, reqBody = drain(req.Body)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func dumpResponse(res *http.Response) ([]byte, error) {
|
func dumpResponse(res *http.Response) ([]byte, error) {
|
||||||
if res.StatusCode == StatusInternalProxyError {
|
if res.StatusCode == StatusInternalProxyError {
|
||||||
// dumps only the body when we have an proxy error.
|
return dumpInternalProxyError(res)
|
||||||
// This body is set in NewProxyHandler()
|
|
||||||
var resBody []byte
|
|
||||||
res.Body, resBody = drain(res.Body)
|
|
||||||
return resBody, nil
|
|
||||||
}
|
}
|
||||||
if res.Header.Get("Content-Encoding") == "gzip" {
|
if res.Header.Get("Content-Encoding") == "gzip" {
|
||||||
var resBody []byte
|
return dumpGzipResponse(res)
|
||||||
res.Body, resBody = drain(res.Body)
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
return httputil.DumpResponse(res, true)
|
return httputil.DumpResponse(res, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dumps only the body when we have an proxy error.
|
||||||
|
// This body is set in NewProxyHandler() in proxy.ErrorHandler
|
||||||
|
func dumpInternalProxyError(res *http.Response) ([]byte, error) {
|
||||||
|
var resBody []byte
|
||||||
|
res.Body, resBody = drain(res.Body)
|
||||||
|
return resBody, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpGzipResponse(res *http.Response) ([]byte, error) {
|
||||||
|
var resBody []byte
|
||||||
|
res.Body, resBody = drain(res.Body)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func drain(b io.ReadCloser) (io.ReadCloser, []byte) {
|
func drain(b io.ReadCloser) (io.ReadCloser, []byte) {
|
||||||
all, _ := ioutil.ReadAll(b)
|
all, _ := ioutil.ReadAll(b)
|
||||||
b.Close()
|
b.Close()
|
||||||
|
|
Loading…
Reference in a new issue