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 captures CaptureList
|
||||
|
||||
// CaptureList stores all captures
|
||||
type CaptureList struct {
|
||||
items []Capture
|
||||
mux sync.Mutex
|
||||
|
@ -17,12 +18,14 @@ type CaptureList struct {
|
|||
Updated chan struct{}
|
||||
}
|
||||
|
||||
// Capture saves our traffic data
|
||||
type Capture struct {
|
||||
ID int
|
||||
Req *http.Request
|
||||
Res *http.Response
|
||||
}
|
||||
|
||||
// CaptureMetadata is the data for each list item in the dashboard
|
||||
type CaptureMetadata struct {
|
||||
ID int `json:"id"`
|
||||
Path string `json:"path"`
|
||||
|
@ -30,12 +33,14 @@ type CaptureMetadata struct {
|
|||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// CaptureDump saves all the dumps shown in the dashboard
|
||||
type CaptureDump struct {
|
||||
Request string `json:"request"`
|
||||
Response string `json:"response"`
|
||||
Curl string `json:"curl"`
|
||||
}
|
||||
|
||||
// Metadata returns the metadada of a capture
|
||||
func (c *Capture) Metadata() CaptureMetadata {
|
||||
return CaptureMetadata{
|
||||
ID: c.ID,
|
||||
|
@ -45,6 +50,7 @@ func (c *Capture) Metadata() CaptureMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
// NewCaptureList creates a new list of captures
|
||||
func NewCaptureList(maxItems int) *CaptureList {
|
||||
return &CaptureList{
|
||||
maxItems: maxItems,
|
||||
|
@ -52,6 +58,7 @@ func NewCaptureList(maxItems int) *CaptureList {
|
|||
}
|
||||
}
|
||||
|
||||
// Insert adds a new capture
|
||||
func (c *CaptureList) Insert(capture Capture) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
@ -63,6 +70,7 @@ func (c *CaptureList) Insert(capture Capture) {
|
|||
c.signalsItemsChange()
|
||||
}
|
||||
|
||||
// Find finds a capture by its id
|
||||
func (c *CaptureList) Find(captureID string) *Capture {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
@ -75,6 +83,7 @@ func (c *CaptureList) Find(captureID string) *Capture {
|
|||
return nil
|
||||
}
|
||||
|
||||
// RemoveAll removes all the captures
|
||||
func (c *CaptureList) RemoveAll() {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
@ -82,10 +91,12 @@ func (c *CaptureList) RemoveAll() {
|
|||
c.signalsItemsChange()
|
||||
}
|
||||
|
||||
// Items returns all the captures
|
||||
func (c *CaptureList) Items() []Capture {
|
||||
return c.items
|
||||
}
|
||||
|
||||
// ItemsAsMetadata returns all the captures as metadata
|
||||
func (c *CaptureList) ItemsAsMetadata() []CaptureMetadata {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// Config has all the configuration parsed from the command line
|
||||
type Config struct {
|
||||
TargetURL string `json:"targetURL"`
|
||||
ProxyPort string `json:"proxyPort"`
|
||||
|
@ -17,6 +18,7 @@ type Config struct {
|
|||
DashboardItemInfoPath string `json:"dashboardItemInfoPath"`
|
||||
}
|
||||
|
||||
// ReadConfig reads the arguments from the command line
|
||||
func ReadConfig() Config {
|
||||
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")
|
||||
|
|
63
main.go
63
main.go
|
@ -33,7 +33,7 @@ func startCapture(config Config) {
|
|||
handler := NewPlugin(NewRecorder(list, NewProxyHandler(config.TargetURL)))
|
||||
|
||||
http.HandleFunc("/", handler)
|
||||
http.HandleFunc(config.DashboardPath, NewDashboardHtmlHandler(config))
|
||||
http.HandleFunc(config.DashboardPath, NewDashboardHTMLHandler(config))
|
||||
http.HandleFunc(config.DashboardConnPath, NewDashboardConnHandler(list))
|
||||
http.HandleFunc(config.DashboardClearPath, NewDashboardClearHandler(list))
|
||||
http.HandleFunc(config.DashboardRetryPath, NewDashboardRetryHandler(list, handler))
|
||||
|
@ -47,6 +47,8 @@ func startCapture(config Config) {
|
|||
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 {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
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 {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
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) {
|
||||
rw.Header().Add("Content-Type", "text/html")
|
||||
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 {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
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 {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
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 {
|
||||
p, err := plugin.Open("plugin.so")
|
||||
if err != nil {
|
||||
|
@ -146,6 +153,7 @@ func NewPlugin(next http.HandlerFunc) http.HandlerFunc {
|
|||
return pluginFn(next)
|
||||
}
|
||||
|
||||
// NewRecorder saves all the traffic data
|
||||
func NewRecorder(list *CaptureList, next http.HandlerFunc) http.HandlerFunc {
|
||||
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 {
|
||||
url, _ := url.Parse(URL)
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
|
@ -205,37 +214,49 @@ func dump(c *Capture) CaptureDump {
|
|||
|
||||
func dumpRequest(req *http.Request) ([]byte, error) {
|
||||
if req.Header.Get("Content-Encoding") == "gzip" {
|
||||
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
|
||||
return dumpGzipRequest(req)
|
||||
}
|
||||
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) {
|
||||
if res.StatusCode == StatusInternalProxyError {
|
||||
// dumps only the body when we have an proxy error.
|
||||
// This body is set in NewProxyHandler()
|
||||
var resBody []byte
|
||||
res.Body, resBody = drain(res.Body)
|
||||
return resBody, nil
|
||||
return dumpInternalProxyError(res)
|
||||
}
|
||||
if res.Header.Get("Content-Encoding") == "gzip" {
|
||||
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
|
||||
return dumpGzipResponse(res)
|
||||
}
|
||||
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) {
|
||||
all, _ := ioutil.ReadAll(b)
|
||||
b.Close()
|
||||
|
|
Loading…
Reference in a new issue