Documentation
¶
Overview ¶
Package httpspy provides utilities for capturing and inspecting HTTP request and response messages.
This package offers configurable capture functions that can snapshot HTTP messages with support for selective header censoring and body collection. It is designed for debugging, logging, and testing scenarios where you need to capture HTTP traffic without permanently modifying the original message headers.
Key Features ¶
- Capture incoming HTTP requests (http.Request) using RequestCaptureWithHeaderCensorshipFunction
- Capture outgoing HTTP requests with RequestOutCaptureWithHeaderCensorshipFunction
- Capture HTTP responses (http.Response) using ResponseCaptureWithHeaderCensorshipFunction
- Selectively censor sensitive headers (e.g., Authorization, Set-Cookie)
- Optionally capture message bodies
- Non-destructive: headers are temporarily modified during capture then restored
Basic Usage ¶
Generate capture functions with the desired options:
// For incoming requests (e.g., in HTTP handlers)
captureRequest := httpspy.RequestCaptureWithHeaderCensorshipFunction(
true, // attemptCollectBody
[]string{"Authorization", "Cookie"}, // headersToCensor
"*** REDACTED ***", // censorText
)
snapshot, err := captureRequest(req)
// For outgoing requests (e.g., in HTTP clients)
captureRequestOut := httpspy.RequestOutCaptureWithHeaderCensorshipFunction(
true, // attemptCollectBody
[]string{"Authorization", "Cookie"}, // headersToCensor
"*** REDACTED ***", // censorText
)
snapshot, err := captureRequestOut(req)
// For responses
captureResponse := httpspy.ResponseCaptureWithHeaderCensorshipFunction(
true, // attemptCollectBody
[]string{"Set-Cookie"}, // headersToCensor
"*** REDACTED ***", // censorText
)
snapshot, err := captureResponse(resp)
Header Censoring ¶
When headersToCensor is specified, the capture functions will temporarily replace the values of specified headers with censorText during the snapshot operation, then restore the original values. This ensures sensitive data in the headers is not included in captured snapshots while keeping the actual HTTP message intact.
Header names are case-insensitive (following HTTP standards).
Index ¶
- func RequestCaptureWithHeaderCensorshipFunction(attemptCollectBody bool, headersToCensor []string, censorText string) func(*http.Request) ([]byte, error)
- func RequestOutCaptureWithHeaderCensorshipFunction(attemptCollectBody bool, headersToCensor []string, censorText string) func(*http.Request) ([]byte, error)
- func ResponseCaptureWithHeaderCensorshipFunction(attemptCollectBody bool, headersToCensor []string, censorText string) func(*http.Response) ([]byte, error)
- type RoundTripTracer
- type RoundTripTracerStatus
- type TraceEvent
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RequestCaptureWithHeaderCensorshipFunction ¶
func RequestCaptureWithHeaderCensorshipFunction(attemptCollectBody bool, headersToCensor []string, censorText string) func(*http.Request) ([]byte, error)
RequestCaptureWithHeaderCensorshipFunction creates a function that captures incoming HTTP requests. If headersToCensor is specified, the returned function will temporarily replace those header values with censorText during capture.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/angrifel/unapologetic/httpspy"
)
func main() {
capture := httpspy.RequestCaptureWithHeaderCensorshipFunction(false, []string{"authorization"}, "[REDACTED]")
req := httptest.NewRequest(http.MethodGet, "https://example.com", nil)
req.Header.Set("Authorization", "Bearer secret-token")
data, _ := capture(req)
fmt.Println(strings.Contains(string(data), "[REDACTED]"))
fmt.Println(!strings.Contains(string(data), "secret-token"))
}
Output: true true
func RequestOutCaptureWithHeaderCensorshipFunction ¶
func RequestOutCaptureWithHeaderCensorshipFunction(attemptCollectBody bool, headersToCensor []string, censorText string) func(*http.Request) ([]byte, error)
RequestOutCaptureWithHeaderCensorshipFunction creates a function that captures outgoing HTTP requests. If headersToCensor is specified, the returned function will temporarily replace those header values with censorText during capture.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/angrifel/unapologetic/httpspy"
)
func main() {
capture := httpspy.RequestOutCaptureWithHeaderCensorshipFunction(true, nil, "")
req := httptest.NewRequest(http.MethodPost, "https://example.com", strings.NewReader("request body"))
req.Header.Set("Content-Type", "text/plain")
data, _ := capture(req)
fmt.Println(len(data) > 0)
}
Output: true
func ResponseCaptureWithHeaderCensorshipFunction ¶
func ResponseCaptureWithHeaderCensorshipFunction(attemptCollectBody bool, headersToCensor []string, censorText string) func(*http.Response) ([]byte, error)
ResponseCaptureWithHeaderCensorshipFunction creates a function that captures HTTP responses. If headersToCensor is specified, the returned function will temporarily replace those header values with censorText during capture.
Example ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"github.com/angrifel/unapologetic/httpspy"
)
func main() {
capture := httpspy.ResponseCaptureWithHeaderCensorshipFunction(true, []string{"set-cookie"}, "[REDACTED]")
resp := &http.Response{
StatusCode: 200,
Status: "200 OK",
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
ContentLength: 13,
Header: http.Header{
"Content-Type": []string{"text/plain"},
"Set-Cookie": []string{"session=abc123"},
},
Body: io.NopCloser(strings.NewReader("response body")),
Request: httptest.NewRequest("GET", "https://example.com", nil),
}
data, _ := capture(resp)
fmt.Println(strings.Contains(string(data), "[REDACTED]"))
fmt.Println(!strings.Contains(string(data), "session=abc123"))
fmt.Println(strings.Contains(string(data), "response body"))
}
Output: true true true
Types ¶
type RoundTripTracer ¶ added in v0.2.0
type RoundTripTracer struct {
// contains filtered or unexported fields
}
RoundTripTracer tracks timing and status information for HTTP round-trip operations. It instruments HTTP requests and responses to capture detailed metrics about connection establishment, data transfer, and lifecycle events.
RoundTripTracer should be used only once per round-trip.
func NewRoundTripTracer ¶ added in v0.2.0
func NewRoundTripTracer() *RoundTripTracer
NewRoundTripTracer creates a new RoundTripTracer for tracking HTTP round-trip metrics. The returned tracer can be used to instrument HTTP requests via TraceRequest and responses via TraceResponse.
Example ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"github.com/angrifel/unapologetic/httpspy"
)
func main() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("hello"))
}))
defer server.Close()
tracer := httpspy.NewRoundTripTracer()
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
req = tracer.TraceRequest(req)
resp, _ := http.DefaultTransport.RoundTrip(req)
tracedResp := tracer.TraceResponse(resp)
body, _ := io.ReadAll(tracedResp.Body)
tracedResp.Body.Close()
status := tracer.Status()
fmt.Println(string(body))
fmt.Println(status.StatusCode)
fmt.Println(status.ConnectionTime > 0)
}
Output: hello 200 true
func NewRoundTripTracerWithNotification ¶ added in v0.2.0
func NewRoundTripTracerWithNotification(notificationMask TraceEvent, notificationChan chan<- RoundTripTracerStatus) *RoundTripTracer
NewRoundTripTracerWithNotification creates a new RoundTripTracer that sends notifications to the provided channel when specific trace events occur.
The notificationMask parameter specifies which events should trigger notifications using bitwise OR of TraceEvent constants (e.g., DNSStart|DNSDone). When a matching event occurs, the current RoundTripTracerStatus is sent to notificationChan.
If notificationChan is nil, no notifications are sent but metrics are still collected.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/angrifel/unapologetic/httpspy"
)
func main() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
ch := make(chan httpspy.RoundTripTracerStatus, 10)
tracer := httpspy.NewRoundTripTracerWithNotification(httpspy.ResponseHeaders, ch)
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
req = tracer.TraceRequest(req)
resp, _ := http.DefaultTransport.RoundTrip(req)
tracedResp := tracer.TraceResponse(resp)
tracedResp.Body.Close()
notification := <-ch
fmt.Println(notification.StatusCode)
}
Output: 200
func (*RoundTripTracer) Status ¶ added in v0.2.0
func (rtt *RoundTripTracer) Status() RoundTripTracerStatus
Status returns a snapshot of the current tracer metrics and status. This method can be called at any time during or after the HTTP round-trip to retrieve the accumulated timing data, status code, errors, and other metrics.
The returned RoundTripTracerStatus contains all collected metrics up to the point of the call. For ongoing operations, some fields may be zero until the corresponding events occur.
func (*RoundTripTracer) TraceRequest ¶ added in v0.2.0
func (rtt *RoundTripTracer) TraceRequest(req *http.Request) *http.Request
TraceRequest instruments an HTTP request to capture detailed timing and lifecycle metrics. It returns a new request with an httptrace.ClientTrace attached to its context.
The tracer captures metrics for connection establishment (DNS, TCP dial, TLS handshake), request transmission, and response timing. The returned request should be used in place of the original request when making the HTTP call.
func (*RoundTripTracer) TraceResponse ¶ added in v0.2.0
func (rtt *RoundTripTracer) TraceResponse(response *http.Response) *http.Response
TraceResponse instruments an HTTP response to capture response body reading metrics. It wraps the response body with instrumentation that tracks bytes read, read errors, and close timing.
The returned response should be used in place of the original response. All operations on the response body (Read, Close) will be tracked and recorded in the tracer's metrics.
This method should be called immediately after receiving a response from an HTTP call that was instrumented with TraceRequest.
func (*RoundTripTracer) TraceRoundTripError ¶ added in v0.2.0
func (rtt *RoundTripTracer) TraceRoundTripError(err error)
TraceRoundTripError records an error that occurred during the HTTP round-trip operation. This method should be called when an error prevents the normal completion of the request, such as connection failures, timeouts, or other transport-level errors.
The error is stored and timing fields (ReceiveTime, CloseTime) are set to mark when the error occurred. The RoundTripError status bit is set and notifications are sent if configured.
type RoundTripTracerStatus ¶ added in v0.2.0
type RoundTripTracerStatus = struct {
// Start is the time when the HTTP request operation began
Start time.Time
// DNSLookupTime represents the duration taken to perform DNS lookup
DNSLookupTime time.Duration
// DialTime represents the duration taken to establish a connection to the server
DialTime time.Duration
// TLSHandshakeTime represents the duration taken to complete the TLS handshake
TLSHandshakeTime time.Duration
// ConnectionTime represents the duration taken to establish the full connection
ConnectionTime time.Duration
// SendTime represents the duration taken to send the request to the server
SendTime time.Duration
// SendError captures any error encountered while writing the request.
SendError error
// FirstByteTime represents the duration until the first byte of the response is received, measured from the beginning of the request.
FirstByteTime time.Duration
// ReceiveTime represents the total duration until the full response body is received, measured from the beginning of the request.
ReceiveTime time.Duration
// CloseTime represents the duration until the connection is closed, measured from the beginning of the request.
CloseTime time.Duration
// ResponseBodyBytesRead represents the number of bytes read from the response body.
ResponseBodyBytesRead int
// StatusCode represents the HTTP status code returned by the server during the round-trip operation.
StatusCode int
// RoundTripError represents any error encountered during the round trip of the request-response cycle.
RoundTripError error
// ResponseBodyReadError represents an error that occurred while reading the response body during the round-trip.
ResponseBodyReadError error
// ResponseBodyCloseError represents an error encountered while closing the response body.
ResponseBodyCloseError error
}
RoundTripTracerStatus contains the collected metrics and status information from an HTTP round-trip operation. All timing fields are measured from the start of the request.
type TraceEvent ¶ added in v0.2.0
type TraceEvent uint64
TraceEvent represents a specific event that occurs during an HTTP round-trip operation. Events can be combined using bitwise OR to create event masks for selective notification.
const ( // GetConn is triggered when the HTTP client begins acquiring a connection. GetConn TraceEvent = 1 << iota // GotConn is triggered when the HTTP client has successfully acquired a connection. GotConn // DNSStart is triggered when DNS lookup begins for the target host. DNSStart // DNSDone is triggered when DNS lookup completes for the target host. DNSDone // ConnectStart is triggered when the TCP connection establishment begins. ConnectStart // ConnectDone is triggered when the TCP connection establishment completes. ConnectDone // TLSHandshakeStart is triggered when the TLS handshake begins (HTTPS only). TLSHandshakeStart // TLSHandshakeDone is triggered when the TLS handshake completes (HTTPS only). TLSHandshakeDone // WroteRequest is triggered when the HTTP request has been written to the connection. WroteRequest // WroteRequestError is triggered when an error occurs while writing the request. WroteRequestError // GotFirstResponseByte is triggered when the first byte of the response is received. GotFirstResponseByte // ResponseHeaders is triggered when response headers have been received. ResponseHeaders // RoundTripError is triggered when an error occurs during the round-trip operation. RoundTripError // BodyRead is triggered each time data is read from the response body. BodyRead // BodyReadEOF is triggered when EOF is reached while reading the response body. BodyReadEOF // BodyReadError is triggered when an error occurs while reading the response body. BodyReadError // BodyClose is triggered when the response body is closed. BodyClose // BodyCloseError is triggered when an error occurs while closing the response body. BodyCloseError )