Skip to content

Commit

Permalink
Curl exporter (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
instabledesign authored Feb 9, 2021
1 parent 1cebe65 commit e855576
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 0 deletions.
18 changes: 18 additions & 0 deletions middleware/request_listener.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package middleware

import (
"net/http"

"github.com/gol4ng/httpware/v4"
)

func RequestListener(listeners ...func(*http.Request)) httpware.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
for _, listener := range listeners {
listener(request)
}
next.ServeHTTP(writer, request)
})
}
}
36 changes: 36 additions & 0 deletions middleware/request_listener_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package middleware_test

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/gol4ng/httpware/v4/middleware"
"github.com/stretchr/testify/assert"
)

func TestRequestListener(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://fake-addr", ioutil.NopCloser(strings.NewReader(url.Values{
"mykey": {"myvalue"},
}.Encode())))
responseWriter := &httptest.ResponseRecorder{}

handlerCalled := false
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, req, r)
handlerCalled = true
})

called := false
listenerMock := func(innerReq *http.Request) {
called = true
assert.Equal(t, req, innerReq)
}

middleware.RequestListener(listenerMock)(handler).ServeHTTP(responseWriter, req)
assert.True(t, called)
assert.True(t, handlerCalled)
}
86 changes: 86 additions & 0 deletions request_listener/curl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package request_listener

import (
"bytes"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
)

// CurlLogDumper will log the request using the curl command format
// /!\ Caution request header and Body can be heavy !!
//
// Following example will log curl command if request has header "dump" not empty
// Eg tripperware:
// tripperware.Skip(
// func(request *http.Request) bool {
// return request.Header.Get("dump") != ""
// },
// tripperware.RequestListener(request_listener.CurlLogDumper),
// )
//
// Eg middleware:
// middleware.Skip(
// func(request *http.Request) bool {
// return request.Header.Get("dump") != ""
// },
// middleware.RequestListener(request_listener.CurlLogDumper),
// )
func CurlLogDumper(request *http.Request) {
if request == nil {
return
}
cmd, err := GetCurlCommand(request)
if err != nil {
log.Println("cannot print curl command", err)
return
}
log.Println(cmd)
}

func GetCurlCommand(req *http.Request) (*Cmd, error) {
cmd := &Cmd{
"curl",
"-X", escape(req.Method),
escape(req.URL.String()),
}

if req.Body != nil {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
req.Body = nopCloser{bytes.NewBuffer(body)}
if len(string(body)) > 0 {
cmd.append("-d", escape(string(body)))
}
}

for h := range req.Header {
cmd.append("-H", escape(h+": "+strings.Join(req.Header[h], " ")))
}

return cmd, nil
}

type Cmd []string

func (c *Cmd) append(newSlice ...string) {
*c = append(*c, newSlice...)
}

func (c *Cmd) String() string {
return strings.Join(*c, " ")
}

type nopCloser struct {
io.Reader
}

func (nopCloser) Close() error { return nil }

func escape(str string) string {
return `'` + strings.Replace(str, `'`, `'\''`, -1) + `'`
}
39 changes: 39 additions & 0 deletions request_listener/curl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package request_listener_test

import (
"bytes"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/gol4ng/httpware/v4/request_listener"
"github.com/stretchr/testify/assert"
)

func TestCurlLogDumper(t *testing.T) {
b := bytes.NewBuffer([]byte{})
log.SetFlags(0)
log.SetOutput(b)

req := httptest.NewRequest(http.MethodGet, "http://fake-addr", ioutil.NopCloser(strings.NewReader(url.Values{
"mykey": {"myvalue"},
}.Encode())))

request_listener.CurlLogDumper(req)
assert.Equal(t, "curl -X 'GET' 'http://fake-addr' -d 'mykey=myvalue'\n", b.String())
}

func TestGetCurlCommand(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://fake-addr", ioutil.NopCloser(strings.NewReader(url.Values{
"mykey": {"myvalue"},
}.Encode())))

cmd, err := request_listener.GetCurlCommand(req)
assert.NoError(t, err)
assert.Len(t, *cmd, 6)
assert.Equal(t, "curl -X 'GET' 'http://fake-addr' -d 'mykey=myvalue'", cmd.String())
}
18 changes: 18 additions & 0 deletions tripperware/request_listener.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package tripperware

import (
"net/http"

"github.com/gol4ng/httpware/v4"
)

func RequestListener(listeners ...func(*http.Request)) httpware.Tripperware {
return func(next http.RoundTripper) http.RoundTripper {
return httpware.RoundTripFunc(func(request *http.Request) (*http.Response, error) {
for _, listener := range listeners {
listener(request)
}
return next.RoundTrip(request)
})
}
}
41 changes: 41 additions & 0 deletions tripperware/request_listener_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package tripperware_test

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

"github.com/gol4ng/httpware/v4/mocks"
"github.com/gol4ng/httpware/v4/tripperware"
)

func TestRequestListener(t *testing.T) {
roundTripperMock := &mocks.RoundTripper{}
req := httptest.NewRequest(http.MethodGet, "http://fake-addr", ioutil.NopCloser(strings.NewReader(url.Values{
"mykey": {"myvalue"},
}.Encode())))

resp := &http.Response{
Status: "OK",
StatusCode: http.StatusOK,
ContentLength: 30,
}

roundTripperMock.On("RoundTrip", mock.AnythingOfType("*http.Request")).Return(resp, nil)

called := false
listenerMock := func(innerReq *http.Request) {
called = true
assert.Equal(t, req, innerReq)
}
resp2, err := tripperware.RequestListener(listenerMock)(roundTripperMock).RoundTrip(req)
assert.Nil(t, err)
assert.Equal(t, resp, resp2)
assert.True(t, called)
}

0 comments on commit e855576

Please sign in to comment.