forked from elazarl/goproxy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port HAR logging from abourget/goproxy to ext package. Closes elazarl#609
- Loading branch information
1 parent
408830d
commit 97bba32
Showing
5 changed files
with
592 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package har | ||
|
||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"os" | ||
"sync" | ||
"time" | ||
|
||
"github.com/elazarl/goproxy" | ||
) | ||
|
||
// Logger implements a HAR logging extension for goproxy | ||
type Logger struct { | ||
mu sync.Mutex | ||
har *Har | ||
captureContent bool | ||
} | ||
|
||
// NewLogger creates a new HAR logger instance | ||
func NewLogger() *Logger { | ||
return &Logger{ | ||
har: New(), | ||
} | ||
} | ||
|
||
// OnRequest handles incoming HTTP requests | ||
func (l *Logger) OnRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { | ||
// Store the start time in context for later use | ||
if ctx != nil { | ||
ctx.UserData = time.Now() | ||
} | ||
return req, nil | ||
} | ||
|
||
// OnResponse handles HTTP responses | ||
func (l *Logger) OnResponse(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { | ||
if resp == nil || ctx == nil || ctx.Req == nil || ctx.UserData == nil { | ||
return resp | ||
} | ||
|
||
startTime, ok := ctx.UserData.(time.Time) | ||
if !ok { | ||
return resp | ||
} | ||
|
||
// Create HAR entry | ||
entry := Entry{ | ||
StartedDateTime: startTime, | ||
Time: time.Since(startTime).Milliseconds(), | ||
Request: ParseRequest(ctx.Req, l.captureContent), | ||
Response: ParseResponse(resp, l.captureContent), | ||
Cache: Cache{}, | ||
Timings: Timings{ | ||
Send: 0, | ||
Wait: time.Since(startTime).Milliseconds(), | ||
Receive: 0, | ||
}, | ||
} | ||
|
||
// Add server IP | ||
entry.FillIPAddress(ctx.Req) | ||
|
||
// Add to HAR log thread-safely | ||
l.mu.Lock() | ||
l.har.AppendEntry(entry) | ||
l.mu.Unlock() | ||
|
||
return resp | ||
} | ||
|
||
// SetCaptureContent enables or disables request/response body capture | ||
func (l *Logger) SetCaptureContent(capture bool) { | ||
l.mu.Lock() | ||
defer l.mu.Unlock() | ||
l.captureContent = capture | ||
} | ||
|
||
// SaveToFile writes the current HAR log to a file | ||
func (l *Logger) SaveToFile(filename string) error { | ||
l.mu.Lock() | ||
defer l.mu.Unlock() | ||
|
||
file, err := os.Create(filename) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
encoder := json.NewEncoder(file) | ||
encoder.SetIndent("", " ") | ||
return encoder.Encode(l.har) | ||
} | ||
|
||
// Clear resets the HAR log | ||
func (l *Logger) Clear() { | ||
l.mu.Lock() | ||
defer l.mu.Unlock() | ||
l.har = New() | ||
} | ||
|
||
// GetEntries returns a copy of the current HAR entries | ||
func (l *Logger) GetEntries() []Entry { | ||
l.mu.Lock() | ||
defer l.mu.Unlock() | ||
entries := make([]Entry, len(l.har.Log.Entries)) | ||
copy(entries, l.har.Log.Entries) | ||
return entries | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
|
||
package har_test | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"net/url" | ||
"os" | ||
"testing" | ||
|
||
"github.com/elazarl/goproxy" | ||
"github.com/elazarl/goproxy/ext/har" | ||
) | ||
|
||
type ConstantHandler string | ||
|
||
func (h ConstantHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
io.WriteString(w, string(h)) | ||
} | ||
|
||
func oneShotProxy(proxy *goproxy.ProxyHttpServer) (client *http.Client, s *httptest.Server) { | ||
s = httptest.NewServer(proxy) | ||
|
||
proxyUrl, _ := url.Parse(s.URL) | ||
tr := &http.Transport{Proxy: http.ProxyURL(proxyUrl)} | ||
client = &http.Client{Transport: tr} | ||
return | ||
} | ||
|
||
func TestHarLogger(t *testing.T) { | ||
// Create a response we expect | ||
expected := "hello world" | ||
background := httptest.NewServer(ConstantHandler(expected)) | ||
defer background.Close() | ||
|
||
// Set up the proxy with HAR logger | ||
proxy := goproxy.NewProxyHttpServer() | ||
logger := har.NewLogger() | ||
logger.SetCaptureContent(true) | ||
|
||
proxy.OnRequest().DoFunc(logger.OnRequest) | ||
proxy.OnResponse().DoFunc(logger.OnResponse) | ||
|
||
client, proxyserver := oneShotProxy(proxy) | ||
defer proxyserver.Close() | ||
|
||
// Make a request | ||
resp, err := client.Get(background.URL) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Read the response | ||
msg, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
resp.Body.Close() | ||
|
||
if string(msg) != expected { | ||
t.Errorf("Expected '%s', actual '%s'", expected, string(msg)) | ||
} | ||
|
||
// Test POST request with content | ||
postData := "test=value" | ||
req, err := http.NewRequest("POST", background.URL, bytes.NewBufferString(postData)) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") | ||
|
||
resp, err = client.Do(req) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
resp.Body.Close() | ||
|
||
// Save HAR file and verify content | ||
tmpfile := "test.har" | ||
err = logger.SaveToFile(tmpfile) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer os.Remove(tmpfile) | ||
|
||
// Read and verify HAR content | ||
harData, err := os.ReadFile(tmpfile) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
var harLog har.Har | ||
if err := json.Unmarshal(harData, &harLog); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Verify we captured both requests | ||
if len(harLog.Log.Entries) != 2 { | ||
t.Errorf("Expected 2 entries in HAR log, got %d", len(harLog.Log.Entries)) | ||
} | ||
|
||
// Verify GET request | ||
if harLog.Log.Entries[0].Request.Method != "GET" { | ||
t.Errorf("Expected GET request, got %s", harLog.Log.Entries[0].Request.Method) | ||
} | ||
|
||
// Verify POST request | ||
if harLog.Log.Entries[1].Request.Method != "POST" { | ||
t.Errorf("Expected POST request, got %s", harLog.Log.Entries[1].Request.Method) | ||
} | ||
} |
Oops, something went wrong.