better responsibilities

This commit is contained in:
Fabricio 2018-08-03 20:53:54 -03:00
parent 1dd6e592e0
commit 665b4579cb
4 changed files with 93 additions and 89 deletions

10
args.go
View file

@ -6,18 +6,18 @@ import (
)
type Args struct {
url *url.URL
port string
targetURL *url.URL
proxyPort string
dashboard string
maxCaptures int
}
func (a *Args) Parse() Args {
proxyURL := flag.String("url", "https://jsonplaceholder.typicode.com", "Required. Set the base url you want to capture")
func ParseArgs() Args {
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")
dashboard := flag.String("dashboard", "dashboard", "Set the dashboard name")
maxCaptures := flag.Int("max-captures", 16, "Set the max number of captures to show in the dashboard")
flag.Parse()
url, _ := url.Parse(*proxyURL)
url, _ := url.Parse(*targetURL)
return Args{url, *proxyPort, *dashboard, *maxCaptures}
}

View file

@ -1,43 +1,46 @@
package main
import "strconv"
import "fmt"
type Capture struct {
ID int `json:"id"`
Path string `json:"path"`
Method string `json:"method"`
Status int `json:"status"`
InfoPath string `json:"infoPath"`
Request string `json:"request"`
Response string `json:"response"`
}
type CaptureRef struct {
ID int `json:"id"`
Path string `json:"path"`
Method string `json:"method"`
Status int `json:"status"`
ItemUrl string `json:"itemUrl"`
type CaptureMetadata struct {
ID int `json:"id"`
Path string `json:"path"`
Method string `json:"method"`
Status int `json:"status"`
InfoPath string `json:"infoPath"`
}
type Captures []Capture
func (items *Captures) Add(capture Capture) {
*items = append(*items, capture)
size := len(*items)
if size > args.maxCaptures {
}
func (items *Captures) RemoveLastAfterReaching(maxItems int) {
if len(*items) > maxItems {
*items = (*items)[1:]
}
}
func (items *Captures) ToReferences(itemBaseUrl string) []CaptureRef {
refs := make([]CaptureRef, len(*items))
func (items *Captures) MetadataOnly() []CaptureMetadata {
refs := make([]CaptureMetadata, len(*items))
for i, item := range *items {
refs[i] = CaptureRef{
ID: item.ID,
Path: item.Path,
Method: item.Method,
Status: item.Status,
ItemUrl: itemBaseUrl + strconv.Itoa(i),
refs[i] = CaptureMetadata{
ID: item.ID,
Path: item.Path,
Method: item.Method,
Status: item.Status,
InfoPath: fmt.Sprintf("%s%d", item.InfoPath, i),
}
}
return refs

View file

@ -6,8 +6,8 @@ const dashboardHTML = `
<head>
<meta charset="utf-8">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.2/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.slim.js"></script>
<link href="https://fonts.googleapis.com/css?family=Inconsolata:400,700" rel="stylesheet">
<title>Dashboard</title>
<style>
@ -134,7 +134,7 @@ const dashboardHTML = `
<div class="list">
<div class="list-inner">
<div class="list-item" ng-repeat="item in items | orderBy: '-id' track by item.id" ng-click="show(item)"
<div class="list-item" ng-repeat="item in items | orderBy: '-id' track by $index" ng-click="show(item)"
ng-class="{selected: isItemSelected(item)}">
<span class="method" ng-class="item.method">{{item.method}}</span>
<span class="path">&lrm;{{item.path}}&lrm;</span>
@ -166,7 +166,7 @@ const dashboardHTML = `
$scope.show = item => {
$scope.path = item.path;
$scope.selectedId = item.id;
$http.get(item.itemUrl).then(r => {
$http.get(item.infoPath).then(r => {
$scope.request = r.data.request;
$scope.response = r.data.response;
$scope.canPrettifyRequestBody = r.data.request.indexOf('Content-Type: application/json') != -1;

127
main.go
View file

@ -9,6 +9,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
@ -17,74 +18,55 @@ import (
type transport struct {
http.RoundTripper
itemInfoPath string
maxItems int
currItemID int
}
var captureID = 0
var captures Captures
var socket socketio.Socket
var host string
var dashboardPath string
var dashboardItemPath string
var args Args
var dashboardSocket socketio.Socket
func main() {
args = args.Parse()
args := ParseArgs()
dashboardPath = fmt.Sprintf("/%s/", args.dashboard)
dashboardItemPath = fmt.Sprintf("/%s/items/", args.dashboard)
proxyHost := fmt.Sprintf("http://localhost:%s", args.proxyPort)
dashboardPath := fmt.Sprintf("/%s/", args.dashboard)
dashboardItemInfoPath := fmt.Sprintf("/%s/items/", args.dashboard)
http.Handle("/", getProxyHandler())
http.Handle("/socket.io/", getSocketHandler())
http.Handle(dashboardItemPath, getDashboardItemHandler())
http.Handle(dashboardPath, getDashboardHandler())
host = fmt.Sprintf("http://localhost:%s", args.port)
fmt.Printf("\nListening on %s", host)
fmt.Printf("\n %s/%s\n\n", host, args.dashboard)
if err := http.ListenAndServe(":"+args.port, nil); err != nil {
fmt.Println(err)
transp := &transport{
RoundTripper: http.DefaultTransport,
itemInfoPath: dashboardItemInfoPath,
maxItems: args.maxCaptures,
currItemID: 0,
}
http.Handle("/", getProxyHandler(args.targetURL, transp))
http.Handle("/socket.io/", getDashboardSocketHandler())
http.Handle(dashboardPath, getDashboardHandler())
http.Handle(dashboardItemInfoPath, getDashboardItemInfoHandler())
fmt.Printf("\nListening on %s", proxyHost)
fmt.Printf("\n %s/%s\n\n", proxyHost, args.dashboard)
fmt.Println(http.ListenAndServe(":"+args.proxyPort, nil))
}
func getProxyHandler() http.Handler {
proxy := httputil.NewSingleHostReverseProxy(args.url)
proxy.Transport = transport{http.DefaultTransport}
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
request.Host = request.URL.Host
proxy.ServeHTTP(response, request)
})
}
func getSocketHandler() http.Handler {
func getDashboardSocketHandler() http.Handler {
server, err := socketio.NewServer(nil)
if err != nil {
panic(err)
fmt.Println("socket server error", err)
}
server.On("connection", func(so socketio.Socket) {
socket = so
emit()
dashboardSocket = so
dashboardSocket.Emit("captures", captures.MetadataOnly())
})
server.On("error", func(so socketio.Socket, err error) {
fmt.Println("socket error:", err)
fmt.Println("socket error", err)
})
return server
}
func getDashboardItemHandler() http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
id := strings.TrimPrefix(req.URL.Path, dashboardItemPath)
i, _ := strconv.Atoi(id)
json, _ := json.Marshal(captures[i])
res.Header().Add("Content-Type", "application/json")
res.Write([]byte(json))
})
}
func getDashboardHandler() http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Add("Content-Type", "text/html")
@ -92,9 +74,28 @@ func getDashboardHandler() http.Handler {
})
}
func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
func getDashboardItemInfoHandler() http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
id := req.URL.Path[strings.LastIndex(req.URL.Path, "/")+1:]
i, _ := strconv.Atoi(id)
json, _ := json.Marshal(captures[i])
res.Header().Add("Content-Type", "application/json")
res.Write([]byte(json))
})
}
reqDump, err := DumpRequest(req)
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)
})
}
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
reqDump, err := dumpRequest(req)
if err != nil {
return nil, err
}
@ -104,32 +105,39 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
return nil, fmt.Errorf("uh oh | %v | %s", err, req.URL)
}
resDump, err := DumpResponse(res)
resDump, err := dumpResponse(res)
if err != nil {
return nil, err
}
capture := Capture{
ID: captureID,
ID: t.NewItemID(),
Path: req.URL.Path,
Method: req.Method,
Status: res.StatusCode,
InfoPath: t.itemInfoPath,
Request: string(reqDump),
Response: string(resDump),
}
captureID++
captures.Add(capture)
emit()
captures.RemoveLastAfterReaching(t.maxItems)
if dashboardSocket != nil {
dashboardSocket.Emit("captures", captures.MetadataOnly())
}
return res, nil
}
func DumpRequest(req *http.Request) ([]byte, error) {
func (t *transport) NewItemID() int {
t.currItemID++
return t.currItemID
}
func dumpRequest(req *http.Request) ([]byte, error) {
return httputil.DumpRequest(req, true)
}
func DumpResponse(res *http.Response) ([]byte, error) {
func dumpResponse(res *http.Response) ([]byte, error) {
var originalBody bytes.Buffer
reader := io.TeeReader(res.Body, &originalBody)
if res.Header.Get("Content-Encoding") == "gzip" {
@ -140,10 +148,3 @@ func DumpResponse(res *http.Response) ([]byte, error) {
res.Body = ioutil.NopCloser(&originalBody)
return resDump, err
}
func emit() {
if socket == nil {
return
}
socket.Emit("captures", captures.ToReferences(host+dashboardItemPath))
}