Skip to content

Commit

Permalink
feat: allow Notify to receive *http.Request (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
rykov authored Sep 16, 2024
1 parent 401a3e6 commit b0d2af0
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 12 deletions.
13 changes: 1 addition & 12 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package honeybadger

import (
"net/http"
"strings"
)

// The Payload interface is implemented by any type which can be handled by the
Expand Down Expand Up @@ -98,7 +97,7 @@ func (client *Client) Handler(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
client.Notify(newError(err, 2), Params(r.Form), getCGIData(r), *r.URL)
client.Notify(newError(err, 2), r)
panic(err)
}
}()
Expand All @@ -120,13 +119,3 @@ func New(c Configuration) *Client {

return &client
}

func getCGIData(request *http.Request) CGIData {
cgiData := CGIData{}
replacer := strings.NewReplacer("-", "_")
for k, v := range request.Header {
key := "HTTP_" + replacer.Replace(strings.ToUpper(k))
cgiData[key] = v[0]
}
return cgiData
}
67 changes: 67 additions & 0 deletions honeybadger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"

Expand Down Expand Up @@ -195,6 +196,72 @@ func TestNotifyWithFingerprint(t *testing.T) {
}
}

func TestNotifyWithRequest(t *testing.T) {
setup(t)
defer teardown()

reqUrl := "/reqPath?qKey=qValue"
var req *http.Request

// Make sure nil request doesn't panic
Notify("Cobras!", req)

// Test a request with query data without form
req = httptest.NewRequest("GET", reqUrl, nil)
Notify("Cobras!", req)
Flush()

// Test a request with form and query data
req = httptest.NewRequest("GET", reqUrl, nil)
req.Header.Set("Accept", "application/test-data")
req.Form = url.Values{"fKey": {"fValue"}}
Notify("Cobras!", req)
Flush()

if !testRequestCount(t, 3) {
return
}

// Request[0] - Valid error means we properly handled a nil value
if error := requests[0].decodeJSON()["error"]; error == nil {
t.Errorf("Request error should be populated.")
}

// Request[1] - Checks URL & query extraction
payload := requests[1].decodeJSON()
request_payload, _ := payload["request"].(map[string]interface{})

if url, _ := request_payload["url"].(string); url != reqUrl {
t.Errorf("Request URL should be extracted. expected=%v actual=%#v.", "/fail", url)
return
}

params, _ := request_payload["params"].(map[string]interface{})
values, _ := params["qKey"].([]interface{})
if len(params) != 1 || len(values) != 1 || values[0] != "qValue" {
t.Errorf("Request params should be extracted. expected=%v actual=%#v.", req.Form, params)
}

// Request[2] - Checks header & form extraction
payload = requests[2].decodeJSON()
request_payload, _ = payload["request"].(map[string]interface{})

if !testNoticePayload(t, payload) {
return
}

cgi, _ := request_payload["cgi_data"].(map[string]interface{})
if len(cgi) != 1 || cgi["HTTP_ACCEPT"] != "application/test-data" {
t.Errorf("Request cgi_data should be extracted. expected=%v actual=%#v.", req.Header, cgi)
}

params, _ = request_payload["params"].(map[string]interface{})
values, _ = params["fKey"].([]interface{})
if len(params) != 1 || len(values) != 1 || values[0] != "fValue" {
t.Errorf("Request params should be extracted. expected=%v actual=%#v.", req.Form, params)
}
}

func TestMonitor(t *testing.T) {
setup(t)
defer teardown()
Expand Down
28 changes: 28 additions & 0 deletions notice.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package honeybadger

import (
"encoding/json"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"time"

"github.com/pborman/uuid"
Expand Down Expand Up @@ -174,8 +176,34 @@ func newNotice(config *Configuration, err Error, extra ...interface{}) *Notice {
notice.CGIData = t
case url.URL:
notice.URL = t.String()
case *http.Request:
setHttpRequest(&notice, t)
}
}

return &notice
}

func setHttpRequest(notice *Notice, r *http.Request) {
if r == nil {
return
}

notice.URL = r.URL.String()
notice.CGIData = CGIData{}

replacer := strings.NewReplacer("-", "_")
for k, v := range r.Header {
key := "HTTP_" + replacer.Replace(strings.ToUpper(k))
notice.CGIData[key] = v[0]
}

// Form is only populated if ParseForm() is called on the request and it
// will include URL query parameters. So if it's empty, then it's possible
// that ParseForm wasn't called, and we will miss reporting URL params.
if form := r.Form; len(form) == 0 {
notice.Params = Params(r.URL.Query())
} else {
notice.Params = Params(form)
}
}

0 comments on commit b0d2af0

Please sign in to comment.