Skip to content

Commit

Permalink
better http handler API (#1)
Browse files Browse the repository at this point in the history
* refactor handler to be more idiomatic

* update makefile with more stuff

* Refactor beacon parser to use first form element

* cleanup ci make task and fix a woopsies

* update readme
  • Loading branch information
adamveld12 authored Jul 17, 2018
1 parent 848708a commit ac5a94e
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 54 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ goboom -address "127.0.0.1:3000" -origin '.*\\.example\\.com' -url "/beacon"

```golang
func main() {
gb := goboom.Goboom{
URL: "/beacon",
gb := goboom.Goboom {
Exporter: goboom.ConsoleExporter(os.Stdout),
}

Expand Down
9 changes: 5 additions & 4 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ var (

func main() {
flag.Parse()
gb := goboom.Goboom{
Method: "POST",
URL: *url,
mux := http.NewServeMux()
gb := goboom.Handler{
Exporter: goboom.ConsoleExporter(os.Stdout),
}

Expand All @@ -33,8 +32,10 @@ func main() {
}
}

mux.Handle(*url, gb)

fmt.Printf("Listening @ %s for HTTP calls on \"%s\"\n", *address, *url)
if err := http.ListenAndServe(*address, gb); err != nil {
if err := http.ListenAndServe(*address, mux); err != nil {
fmt.Printf("Could not listen and serve on %s: %v", *address, err)
os.Exit(1)
}
Expand Down
35 changes: 18 additions & 17 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,32 @@ import (
"net/http"
)

// Beacon represents a boomerang beacon
type Beacon struct {
Referer string
Source string
UserAgent string
Metrics Metric
}

type Metric map[string][]string
// Metric is a map of they beacon's metrics payload
type Metric map[string]string

// BeaconValidator validates a request, returning an error if the request should not be handled
type BeaconValidator func(*http.Request) error

// BeaconExporter allows for exporting the beacon or other http.Request info to various backends or services
type BeaconExporter func(*http.Request, Beacon) error

type Goboom struct {
Method string
URL string
// Handler is the beacon http.Handler
type Handler struct {
Validator BeaconValidator
Exporter BeaconExporter
}

func (g Goboom) ServeHTTP(res http.ResponseWriter, req *http.Request) {
if g.Method != "" && req.Method != g.Method {
http.Error(res, "Unexpected http.method", http.StatusMethodNotAllowed)
return
}

if g.URL != "" && req.URL.Path != g.URL {
http.Error(res, "Not Found", http.StatusNotFound)
func (g Handler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
if req.Method != "GET" && req.Method != "POST" {
http.Error(res, fmt.Sprintf("Method not allowed: %s", req.Method), http.StatusMethodNotAllowed)
return
}

Expand Down Expand Up @@ -69,18 +68,20 @@ func parseBeacon(req *http.Request) (Beacon, error) {
result.Metrics = Metric{}

for k, v := range req.Form {
result.Metrics[k] = v
if len(v) > 0 {
result.Metrics[k] = v[0]
}
}
}

if metricURL, ok := result.Metrics["r"]; ok && len(metricURL) > 0 {
result.Referer = metricURL[0]
if metricURL, ok := result.Metrics["r"]; ok {
result.Referer = metricURL
} else if req.Referer() != "" {
result.Referer = req.Referer()
}

if sourceURL, ok := result.Metrics["u"]; ok && len(sourceURL) > 0 && sourceURL[0] != "" {
result.Source = sourceURL[0]
if sourceURL, ok := result.Metrics["u"]; ok && sourceURL != "" {
result.Source = sourceURL
} else if origin := req.Header.Get("Origin"); origin != "" {
result.Source = origin
}
Expand Down
34 changes: 15 additions & 19 deletions handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,18 @@ type methodTestCase struct {

func TestMethodValidation(t *testing.T) {
cases := []methodTestCase{
{Name: "Happy Path", ExpectedMethod: "POST", Method: "POST", ExpectedStatus: 200},
{Name: "Forbidden when not matched", ExpectedMethod: "POST", Method: "GET", ExpectedStatus: 405},
{Name: "Accept anything when unintialized 1", ExpectedMethod: "", Method: "GET", ExpectedStatus: 200},
{Name: "Accept anything when unintialized 2", ExpectedMethod: "", Method: "POST", ExpectedStatus: 200},
{Name: "Accept Post", Method: "POST", ExpectedStatus: 200},
{Name: "Accept Get", Method: "GET", ExpectedStatus: 200},
{Name: "Deny Delete", Method: "DELETE", ExpectedStatus: 405},
{Name: "Deny Head", Method: "HEAD", ExpectedStatus: 405},
{Name: "Deny Options", Method: "OPTIONS", ExpectedStatus: 405},
}

str, _ := os.Open(os.DevNull)
defer str.Close()

for idx, c := range cases {
g := Goboom{
Method: c.ExpectedMethod,
URL: "/",
g := Handler{
Exporter: ConsoleExporter(str),
}

Expand All @@ -47,24 +46,21 @@ func TestMethodValidation(t *testing.T) {

type urlTestCase struct {
Name string
InputURL string
TestURL string
ExpectedStatus int
}

func TestURLValidation(t *testing.T) {
cases := []urlTestCase{
{Name: "Happy Path", InputURL: "/beacon", TestURL: "/beacon", ExpectedStatus: 200},
{Name: "Path doesn't match", InputURL: "/not-beacon-url", TestURL: "/beacon", ExpectedStatus: 404},
{Name: "Not initialized allows anything #1", InputURL: "", TestURL: "/beacon", ExpectedStatus: 200},
{Name: "Not initialized allows anything #2", InputURL: "", TestURL: "/beacon-url-2", ExpectedStatus: 200},
{Name: "Happy Path", TestURL: "/beacon", ExpectedStatus: 200},
{Name: "works with empty path", TestURL: "", ExpectedStatus: 200},
{Name: "works with multi paths", TestURL: "/beacon/url/2", ExpectedStatus: 200},
}
str, _ := os.Open(os.DevNull)
defer str.Close()

for idx, c := range cases {
g := Goboom{
URL: c.InputURL,
g := Handler{
Exporter: ConsoleExporter(str),
}

Expand Down Expand Up @@ -113,9 +109,9 @@ func TestParseBeacon(t *testing.T) {
Referer: "http://boomerang-test.surge.sh/",
Source: "http://boomerang-test.surge.sh/test",
Metrics: Metric{
"r": []string{"http://boomerang-test.surge.sh/"},
"u": []string{"http://boomerang-test.surge.sh/test"},
"c.tti.vr": []string{"665"},
"r": "http://boomerang-test.surge.sh/",
"u": "http://boomerang-test.surge.sh/test",
"c.tti.vr": "665",
},
},
},
Expand All @@ -137,7 +133,7 @@ func TestParseBeacon(t *testing.T) {
c.Name,
c.ExpectedErr,
err)
return
continue
}

if !reflect.DeepEqual(b, c.ExpectedBeacon) {
Expand All @@ -146,7 +142,7 @@ func TestParseBeacon(t *testing.T) {
c.Name,
c.ExpectedBeacon,
b)
return
continue
}
}
}
Expand Down
31 changes: 19 additions & 12 deletions makefile
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
all: clean goboom dev
.PHONY: ci clean dev lint setup test

dev: clean goboom
./goboom \
-address localhost:3000 \
-origin .* \
-url "/beacon"

ci: clean setup lint test goboom

clean:
rm -rf ./goboom

ci: clean
setup:
go get -t -d -v ./...
go test -v -cover ./...
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags netgo -a -v -o ./goboom ./cli

goboom:
go build -o ./goboom ./cli
lint:
go get golang.org/x/lint/golint
golint -set_exit_status
go vet -all -v

dev: clean goboom
./goboom \
-address localhost:3000 \
-origin .* \
-url "/beacon"
test:
go test -v -cover ./...

.PHONY: all ci clean dev

goboom:
go build -o ./goboom ./cli

0 comments on commit ac5a94e

Please sign in to comment.