mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Replace GraphQL server with Connect RPC
This commit is contained in:
@ -3,15 +3,13 @@ package reqlog
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
FindRequestLogs(ctx context.Context, filter FindRequestsFilter, scope *scope.Scope) ([]RequestLog, error)
|
||||
FindRequestLogByID(ctx context.Context, projectID, id ulid.ULID) (RequestLog, error)
|
||||
StoreRequestLog(ctx context.Context, reqLog RequestLog) error
|
||||
StoreResponseLog(ctx context.Context, projectID, reqLogID ulid.ULID, resLog ResponseLog) error
|
||||
ClearRequestLogs(ctx context.Context, projectID ulid.ULID) error
|
||||
FindRequestLogs(ctx context.Context, projectID string, filterFn func(*HttpRequestLog) (bool, error)) ([]*HttpRequestLog, error)
|
||||
FindRequestLogByID(ctx context.Context, projectID, id string) (*HttpRequestLog, error)
|
||||
StoreRequestLog(ctx context.Context, reqLog *HttpRequestLog) error
|
||||
StoreResponseLog(ctx context.Context, projectID, reqLogID string, resLog *httppb.Response) error
|
||||
ClearRequestLogs(ctx context.Context, projectID string) error
|
||||
}
|
||||
|
167
pkg/reqlog/reqlog.connect.go
Normal file
167
pkg/reqlog/reqlog.connect.go
Normal file
@ -0,0 +1,167 @@
|
||||
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
|
||||
//
|
||||
// Source: reqlog/reqlog.proto
|
||||
|
||||
package reqlog
|
||||
|
||||
import (
|
||||
connect "connectrpc.com/connect"
|
||||
context "context"
|
||||
errors "errors"
|
||||
http "net/http"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the connect package are
|
||||
// compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of connect newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of connect or updating the connect
|
||||
// version compiled into your binary.
|
||||
const _ = connect.IsAtLeastVersion1_13_0
|
||||
|
||||
const (
|
||||
// HttpRequestLogServiceName is the fully-qualified name of the HttpRequestLogService service.
|
||||
HttpRequestLogServiceName = "hetty.reqlog.v1.HttpRequestLogService"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// HttpRequestLogServiceGetHttpRequestLogProcedure is the fully-qualified name of the
|
||||
// HttpRequestLogService's GetHttpRequestLog RPC.
|
||||
HttpRequestLogServiceGetHttpRequestLogProcedure = "/hetty.reqlog.v1.HttpRequestLogService/GetHttpRequestLog"
|
||||
// HttpRequestLogServiceListHttpRequestLogsProcedure is the fully-qualified name of the
|
||||
// HttpRequestLogService's ListHttpRequestLogs RPC.
|
||||
HttpRequestLogServiceListHttpRequestLogsProcedure = "/hetty.reqlog.v1.HttpRequestLogService/ListHttpRequestLogs"
|
||||
// HttpRequestLogServiceClearHttpRequestLogsProcedure is the fully-qualified name of the
|
||||
// HttpRequestLogService's ClearHttpRequestLogs RPC.
|
||||
HttpRequestLogServiceClearHttpRequestLogsProcedure = "/hetty.reqlog.v1.HttpRequestLogService/ClearHttpRequestLogs"
|
||||
)
|
||||
|
||||
// HttpRequestLogServiceClient is a client for the hetty.reqlog.v1.HttpRequestLogService service.
|
||||
type HttpRequestLogServiceClient interface {
|
||||
GetHttpRequestLog(context.Context, *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error)
|
||||
ListHttpRequestLogs(context.Context, *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error)
|
||||
ClearHttpRequestLogs(context.Context, *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error)
|
||||
}
|
||||
|
||||
// NewHttpRequestLogServiceClient constructs a client for the hetty.reqlog.v1.HttpRequestLogService
|
||||
// service. By default, it uses the Connect protocol with the binary Protobuf Codec, asks for
|
||||
// gzipped responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply
|
||||
// the connect.WithGRPC() or connect.WithGRPCWeb() options.
|
||||
//
|
||||
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
|
||||
// http://api.acme.com or https://acme.com/grpc).
|
||||
func NewHttpRequestLogServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) HttpRequestLogServiceClient {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
httpRequestLogServiceMethods := File_reqlog_reqlog_proto.Services().ByName("HttpRequestLogService").Methods()
|
||||
return &httpRequestLogServiceClient{
|
||||
getHttpRequestLog: connect.NewClient[GetHttpRequestLogRequest, GetHttpRequestLogResponse](
|
||||
httpClient,
|
||||
baseURL+HttpRequestLogServiceGetHttpRequestLogProcedure,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("GetHttpRequestLog")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
listHttpRequestLogs: connect.NewClient[ListHttpRequestLogsRequest, ListHttpRequestLogsResponse](
|
||||
httpClient,
|
||||
baseURL+HttpRequestLogServiceListHttpRequestLogsProcedure,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("ListHttpRequestLogs")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
clearHttpRequestLogs: connect.NewClient[ClearHttpRequestLogsRequest, ClearHttpRequestLogsResponse](
|
||||
httpClient,
|
||||
baseURL+HttpRequestLogServiceClearHttpRequestLogsProcedure,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("ClearHttpRequestLogs")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// httpRequestLogServiceClient implements HttpRequestLogServiceClient.
|
||||
type httpRequestLogServiceClient struct {
|
||||
getHttpRequestLog *connect.Client[GetHttpRequestLogRequest, GetHttpRequestLogResponse]
|
||||
listHttpRequestLogs *connect.Client[ListHttpRequestLogsRequest, ListHttpRequestLogsResponse]
|
||||
clearHttpRequestLogs *connect.Client[ClearHttpRequestLogsRequest, ClearHttpRequestLogsResponse]
|
||||
}
|
||||
|
||||
// GetHttpRequestLog calls hetty.reqlog.v1.HttpRequestLogService.GetHttpRequestLog.
|
||||
func (c *httpRequestLogServiceClient) GetHttpRequestLog(ctx context.Context, req *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error) {
|
||||
return c.getHttpRequestLog.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ListHttpRequestLogs calls hetty.reqlog.v1.HttpRequestLogService.ListHttpRequestLogs.
|
||||
func (c *httpRequestLogServiceClient) ListHttpRequestLogs(ctx context.Context, req *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error) {
|
||||
return c.listHttpRequestLogs.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// ClearHttpRequestLogs calls hetty.reqlog.v1.HttpRequestLogService.ClearHttpRequestLogs.
|
||||
func (c *httpRequestLogServiceClient) ClearHttpRequestLogs(ctx context.Context, req *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error) {
|
||||
return c.clearHttpRequestLogs.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// HttpRequestLogServiceHandler is an implementation of the hetty.reqlog.v1.HttpRequestLogService
|
||||
// service.
|
||||
type HttpRequestLogServiceHandler interface {
|
||||
GetHttpRequestLog(context.Context, *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error)
|
||||
ListHttpRequestLogs(context.Context, *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error)
|
||||
ClearHttpRequestLogs(context.Context, *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error)
|
||||
}
|
||||
|
||||
// NewHttpRequestLogServiceHandler builds an HTTP handler from the service implementation. It
|
||||
// returns the path on which to mount the handler and the handler itself.
|
||||
//
|
||||
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
|
||||
// and JSON codecs. They also support gzip compression.
|
||||
func NewHttpRequestLogServiceHandler(svc HttpRequestLogServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||
httpRequestLogServiceMethods := File_reqlog_reqlog_proto.Services().ByName("HttpRequestLogService").Methods()
|
||||
httpRequestLogServiceGetHttpRequestLogHandler := connect.NewUnaryHandler(
|
||||
HttpRequestLogServiceGetHttpRequestLogProcedure,
|
||||
svc.GetHttpRequestLog,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("GetHttpRequestLog")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
httpRequestLogServiceListHttpRequestLogsHandler := connect.NewUnaryHandler(
|
||||
HttpRequestLogServiceListHttpRequestLogsProcedure,
|
||||
svc.ListHttpRequestLogs,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("ListHttpRequestLogs")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
httpRequestLogServiceClearHttpRequestLogsHandler := connect.NewUnaryHandler(
|
||||
HttpRequestLogServiceClearHttpRequestLogsProcedure,
|
||||
svc.ClearHttpRequestLogs,
|
||||
connect.WithSchema(httpRequestLogServiceMethods.ByName("ClearHttpRequestLogs")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/hetty.reqlog.v1.HttpRequestLogService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case HttpRequestLogServiceGetHttpRequestLogProcedure:
|
||||
httpRequestLogServiceGetHttpRequestLogHandler.ServeHTTP(w, r)
|
||||
case HttpRequestLogServiceListHttpRequestLogsProcedure:
|
||||
httpRequestLogServiceListHttpRequestLogsHandler.ServeHTTP(w, r)
|
||||
case HttpRequestLogServiceClearHttpRequestLogsProcedure:
|
||||
httpRequestLogServiceClearHttpRequestLogsHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UnimplementedHttpRequestLogServiceHandler returns CodeUnimplemented from all methods.
|
||||
type UnimplementedHttpRequestLogServiceHandler struct{}
|
||||
|
||||
func (UnimplementedHttpRequestLogServiceHandler) GetHttpRequestLog(context.Context, *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.reqlog.v1.HttpRequestLogService.GetHttpRequestLog is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedHttpRequestLogServiceHandler) ListHttpRequestLogs(context.Context, *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.reqlog.v1.HttpRequestLogService.ListHttpRequestLogs is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedHttpRequestLogServiceHandler) ClearHttpRequestLogs(context.Context, *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hetty.reqlog.v1.HttpRequestLogService.ClearHttpRequestLogs is not implemented"))
|
||||
}
|
@ -6,13 +6,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"connectrpc.com/connect"
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/log"
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
@ -26,48 +26,21 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRequestNotFound = errors.New("reqlog: request not found")
|
||||
ErrRequestLogNotFound = errors.New("reqlog: request not found")
|
||||
ErrProjectIDMustBeSet = errors.New("reqlog: project ID must be set")
|
||||
)
|
||||
|
||||
type RequestLog struct {
|
||||
ID ulid.ULID
|
||||
ProjectID ulid.ULID
|
||||
|
||||
URL *url.URL
|
||||
Method string
|
||||
Proto string
|
||||
Header http.Header
|
||||
Body []byte
|
||||
|
||||
Response *ResponseLog
|
||||
}
|
||||
|
||||
type ResponseLog struct {
|
||||
Proto string
|
||||
StatusCode int
|
||||
Status string
|
||||
Header http.Header
|
||||
Body []byte
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
bypassOutOfScopeRequests bool
|
||||
findReqsFilter FindRequestsFilter
|
||||
activeProjectID ulid.ULID
|
||||
reqsFilter *RequestLogsFilter
|
||||
activeProjectID string
|
||||
scope *scope.Scope
|
||||
repo Repository
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
type FindRequestsFilter struct {
|
||||
ProjectID ulid.ULID
|
||||
OnlyInScope bool
|
||||
SearchExpr filter.Expression
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ActiveProjectID ulid.ULID
|
||||
ActiveProjectID string
|
||||
Scope *scope.Scope
|
||||
Repository Repository
|
||||
Logger log.Logger
|
||||
@ -88,25 +61,92 @@ func NewService(cfg Config) *Service {
|
||||
return s
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequests(ctx context.Context) ([]RequestLog, error) {
|
||||
return svc.repo.FindRequestLogs(ctx, svc.findReqsFilter, svc.scope)
|
||||
func (svc *Service) ListHttpRequestLogs(ctx context.Context, req *connect.Request[ListHttpRequestLogsRequest]) (*connect.Response[ListHttpRequestLogsResponse], error) {
|
||||
projectID := svc.activeProjectID
|
||||
if projectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrProjectIDMustBeSet)
|
||||
}
|
||||
|
||||
reqLogs, err := svc.repo.FindRequestLogs(ctx, projectID, svc.filterRequestLog)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("reqlog: failed to find request logs: %w", err))
|
||||
}
|
||||
|
||||
return connect.NewResponse(&ListHttpRequestLogsResponse{
|
||||
HttpRequestLogs: reqLogs,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequestLogByID(ctx context.Context, id ulid.ULID) (RequestLog, error) {
|
||||
func (svc *Service) filterRequestLog(reqLog *HttpRequestLog) (bool, error) {
|
||||
if svc.reqsFilter.GetOnlyInScope() && svc.scope != nil && !reqLog.MatchScope(svc.scope) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var f filter.Expression
|
||||
var err error
|
||||
if expr := svc.reqsFilter.GetSearchExpr(); expr != "" {
|
||||
f, err = filter.ParseQuery(expr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse search expression: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
match, err := reqLog.Matches(f)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match search expression for request log (id: %v): %w", reqLog.Id, err)
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
func (svc *Service) FindRequestLogByID(ctx context.Context, id string) (*HttpRequestLog, error) {
|
||||
if svc.activeProjectID == "" {
|
||||
return nil, connect.NewError(connect.CodeFailedPrecondition, ErrProjectIDMustBeSet)
|
||||
}
|
||||
|
||||
return svc.repo.FindRequestLogByID(ctx, svc.activeProjectID, id)
|
||||
}
|
||||
|
||||
func (svc *Service) ClearRequests(ctx context.Context, projectID ulid.ULID) error {
|
||||
return svc.repo.ClearRequestLogs(ctx, projectID)
|
||||
// GetHttpRequestLog implements HttpRequestLogServiceHandler.
|
||||
func (svc *Service) GetHttpRequestLog(ctx context.Context, req *connect.Request[GetHttpRequestLogRequest]) (*connect.Response[GetHttpRequestLogResponse], error) {
|
||||
id, err := ulid.Parse(req.Msg.Id)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||
}
|
||||
|
||||
reqLog, err := svc.repo.FindRequestLogByID(ctx, svc.activeProjectID, id.String())
|
||||
if errors.Is(err, ErrRequestLogNotFound) {
|
||||
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, err)
|
||||
}
|
||||
|
||||
return connect.NewResponse(&GetHttpRequestLogResponse{
|
||||
HttpRequestLog: reqLog,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (svc *Service) storeResponse(ctx context.Context, reqLogID ulid.ULID, res *http.Response) error {
|
||||
resLog, err := ParseHTTPResponse(res)
|
||||
func (svc *Service) ClearHttpRequestLogs(ctx context.Context, req *connect.Request[ClearHttpRequestLogsRequest]) (*connect.Response[ClearHttpRequestLogsResponse], error) {
|
||||
err := svc.repo.ClearRequestLogs(ctx, svc.activeProjectID)
|
||||
if err != nil {
|
||||
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("reqlog: failed to clear request logs: %w", err))
|
||||
}
|
||||
|
||||
return connect.NewResponse(&ClearHttpRequestLogsResponse{}), nil
|
||||
}
|
||||
|
||||
func (svc *Service) storeResponse(ctx context.Context, reqLogID string, res *http.Response) error {
|
||||
respb, err := httppb.ParseHTTPResponse(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.repo.StoreResponseLog(ctx, svc.activeProjectID, reqLogID, resLog)
|
||||
return svc.repo.StoreResponseLog(ctx, svc.activeProjectID, reqLogID, respb)
|
||||
}
|
||||
|
||||
func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
|
||||
@ -121,19 +161,19 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
||||
// TODO: Use io.LimitReader.
|
||||
var err error
|
||||
|
||||
body, err = ioutil.ReadAll(req.Body)
|
||||
body, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
svc.logger.Errorw("Failed to read request body for logging.",
|
||||
"error", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
clone.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
req.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
clone.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
|
||||
// Bypass logging if no project is active.
|
||||
if svc.activeProjectID.Compare(ulid.ULID{}) == 0 {
|
||||
if svc.activeProjectID == "" {
|
||||
ctx := context.WithValue(req.Context(), LogBypassedKey, true)
|
||||
*req = *req.WithContext(ctx)
|
||||
|
||||
@ -161,14 +201,37 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
||||
return
|
||||
}
|
||||
|
||||
reqLog := RequestLog{
|
||||
ID: reqID,
|
||||
ProjectID: svc.activeProjectID,
|
||||
Method: clone.Method,
|
||||
URL: clone.URL,
|
||||
Proto: clone.Proto,
|
||||
Header: clone.Header,
|
||||
Body: body,
|
||||
proto, ok := httppb.ProtoMap[clone.Proto]
|
||||
if !ok {
|
||||
svc.logger.Errorw("Bypassed logging: request has an invalid protocol.",
|
||||
"proto", clone.Proto)
|
||||
return
|
||||
}
|
||||
|
||||
method, ok := httppb.MethodMap[clone.Method]
|
||||
if !ok {
|
||||
svc.logger.Errorw("Bypassed logging: request has an invalid method.",
|
||||
"method", clone.Method)
|
||||
return
|
||||
}
|
||||
|
||||
headers := []*httppb.Header{}
|
||||
for k, v := range clone.Header {
|
||||
for _, vv := range v {
|
||||
headers = append(headers, &httppb.Header{Key: k, Value: vv})
|
||||
}
|
||||
}
|
||||
|
||||
reqLog := &HttpRequestLog{
|
||||
Id: reqID.String(),
|
||||
ProjectId: svc.activeProjectID,
|
||||
Request: &httppb.Request{
|
||||
Url: clone.URL.String(),
|
||||
Method: method,
|
||||
Protocol: proto,
|
||||
Headers: headers,
|
||||
Body: body,
|
||||
},
|
||||
}
|
||||
|
||||
err := svc.repo.StoreRequestLog(req.Context(), reqLog)
|
||||
@ -179,10 +242,10 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
|
||||
}
|
||||
|
||||
svc.logger.Debugw("Stored request log.",
|
||||
"reqLogID", reqLog.ID.String(),
|
||||
"url", reqLog.URL.String())
|
||||
|
||||
ctx := context.WithValue(req.Context(), ReqLogIDKey, reqLog.ID)
|
||||
"reqLogID", reqLog.Id,
|
||||
"url", reqLog.Request.Url,
|
||||
)
|
||||
ctx := context.WithValue(req.Context(), ReqLogIDKey, reqID)
|
||||
*req = *req.WithContext(ctx)
|
||||
}
|
||||
}
|
||||
@ -216,7 +279,7 @@ func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := svc.storeResponse(context.Background(), reqLogID, &clone); err != nil {
|
||||
if err := svc.storeResponse(context.Background(), reqLogID.String(), &clone); err != nil {
|
||||
svc.logger.Errorw("Failed to store response log.",
|
||||
"error", err)
|
||||
} else {
|
||||
@ -229,20 +292,16 @@ func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.Respon
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Service) SetActiveProjectID(id ulid.ULID) {
|
||||
func (svc *Service) SetActiveProjectID(id string) {
|
||||
svc.activeProjectID = id
|
||||
}
|
||||
|
||||
func (svc *Service) ActiveProjectID() ulid.ULID {
|
||||
func (svc *Service) ActiveProjectID() string {
|
||||
return svc.activeProjectID
|
||||
}
|
||||
|
||||
func (svc *Service) SetFindReqsFilter(filter FindRequestsFilter) {
|
||||
svc.findReqsFilter = filter
|
||||
}
|
||||
|
||||
func (svc *Service) FindReqsFilter() FindRequestsFilter {
|
||||
return svc.findReqsFilter
|
||||
func (svc *Service) SetRequestLogsFilter(filter *RequestLogsFilter) {
|
||||
svc.reqsFilter = filter
|
||||
}
|
||||
|
||||
func (svc *Service) SetBypassOutOfScopeRequests(bypass bool) {
|
||||
@ -252,18 +311,3 @@ func (svc *Service) SetBypassOutOfScopeRequests(bypass bool) {
|
||||
func (svc *Service) BypassOutOfScopeRequests() bool {
|
||||
return svc.bypassOutOfScopeRequests
|
||||
}
|
||||
|
||||
func ParseHTTPResponse(res *http.Response) (ResponseLog, error) {
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return ResponseLog{}, fmt.Errorf("reqlog: could not read body: %w", err)
|
||||
}
|
||||
|
||||
return ResponseLog{
|
||||
Proto: res.Proto,
|
||||
StatusCode: res.StatusCode,
|
||||
Status: res.Status,
|
||||
Header: res.Header,
|
||||
Body: body,
|
||||
}, nil
|
||||
}
|
||||
|
533
pkg/reqlog/reqlog.pb.go
Normal file
533
pkg/reqlog/reqlog.pb.go
Normal file
@ -0,0 +1,533 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc (unknown)
|
||||
// source: reqlog/reqlog.proto
|
||||
|
||||
package reqlog
|
||||
|
||||
import (
|
||||
http "github.com/dstotijn/hetty/pkg/http"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type HttpRequestLog struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ProjectId string `protobuf:"bytes,2,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"`
|
||||
RemoteIp string `protobuf:"bytes,3,opt,name=remote_ip,json=remoteIp,proto3" json:"remote_ip,omitempty"`
|
||||
Request *http.Request `protobuf:"bytes,4,opt,name=request,proto3" json:"request,omitempty"`
|
||||
Response *http.Response `protobuf:"bytes,5,opt,name=response,proto3" json:"response,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) Reset() {
|
||||
*x = HttpRequestLog{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*HttpRequestLog) ProtoMessage() {}
|
||||
|
||||
func (x *HttpRequestLog) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use HttpRequestLog.ProtoReflect.Descriptor instead.
|
||||
func (*HttpRequestLog) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetProjectId() string {
|
||||
if x != nil {
|
||||
return x.ProjectId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetRemoteIp() string {
|
||||
if x != nil {
|
||||
return x.RemoteIp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetRequest() *http.Request {
|
||||
if x != nil {
|
||||
return x.Request
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *HttpRequestLog) GetResponse() *http.Response {
|
||||
if x != nil {
|
||||
return x.Response
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetHttpRequestLogRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogRequest) Reset() {
|
||||
*x = GetHttpRequestLogRequest{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetHttpRequestLogRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetHttpRequestLogRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetHttpRequestLogRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetHttpRequestLogRequest) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogRequest) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GetHttpRequestLogResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
HttpRequestLog *HttpRequestLog `protobuf:"bytes,1,opt,name=http_request_log,json=httpRequestLog,proto3" json:"http_request_log,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogResponse) Reset() {
|
||||
*x = GetHttpRequestLogResponse{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetHttpRequestLogResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetHttpRequestLogResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetHttpRequestLogResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetHttpRequestLogResponse) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *GetHttpRequestLogResponse) GetHttpRequestLog() *HttpRequestLog {
|
||||
if x != nil {
|
||||
return x.HttpRequestLog
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListHttpRequestLogsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsRequest) Reset() {
|
||||
*x = ListHttpRequestLogsRequest{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListHttpRequestLogsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListHttpRequestLogsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListHttpRequestLogsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListHttpRequestLogsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
type ListHttpRequestLogsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
HttpRequestLogs []*HttpRequestLog `protobuf:"bytes,1,rep,name=http_request_logs,json=httpRequestLogs,proto3" json:"http_request_logs,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsResponse) Reset() {
|
||||
*x = ListHttpRequestLogsResponse{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListHttpRequestLogsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListHttpRequestLogsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListHttpRequestLogsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListHttpRequestLogsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *ListHttpRequestLogsResponse) GetHttpRequestLogs() []*HttpRequestLog {
|
||||
if x != nil {
|
||||
return x.HttpRequestLogs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RequestLogsFilter struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
OnlyInScope bool `protobuf:"varint,1,opt,name=only_in_scope,json=onlyInScope,proto3" json:"only_in_scope,omitempty"`
|
||||
SearchExpr string `protobuf:"bytes,2,opt,name=search_expr,json=searchExpr,proto3" json:"search_expr,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RequestLogsFilter) Reset() {
|
||||
*x = RequestLogsFilter{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RequestLogsFilter) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RequestLogsFilter) ProtoMessage() {}
|
||||
|
||||
func (x *RequestLogsFilter) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RequestLogsFilter.ProtoReflect.Descriptor instead.
|
||||
func (*RequestLogsFilter) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *RequestLogsFilter) GetOnlyInScope() bool {
|
||||
if x != nil {
|
||||
return x.OnlyInScope
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *RequestLogsFilter) GetSearchExpr() string {
|
||||
if x != nil {
|
||||
return x.SearchExpr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ClearHttpRequestLogsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ClearHttpRequestLogsRequest) Reset() {
|
||||
*x = ClearHttpRequestLogsRequest{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ClearHttpRequestLogsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ClearHttpRequestLogsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ClearHttpRequestLogsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ClearHttpRequestLogsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ClearHttpRequestLogsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
type ClearHttpRequestLogsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ClearHttpRequestLogsResponse) Reset() {
|
||||
*x = ClearHttpRequestLogsResponse{}
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ClearHttpRequestLogsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ClearHttpRequestLogsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ClearHttpRequestLogsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_reqlog_reqlog_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ClearHttpRequestLogsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ClearHttpRequestLogsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_reqlog_reqlog_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
var File_reqlog_reqlog_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_reqlog_reqlog_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2f, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71,
|
||||
0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x1a, 0x0f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x68, 0x74, 0x74,
|
||||
0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x01, 0x0a, 0x0e, 0x48, 0x74, 0x74, 0x70,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72,
|
||||
0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||
0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x6d,
|
||||
0x6f, 0x74, 0x65, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65,
|
||||
0x6d, 0x6f, 0x74, 0x65, 0x49, 0x70, 0x12, 0x30, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e,
|
||||
0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52,
|
||||
0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x74,
|
||||
0x74, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x0a,
|
||||
0x18, 0x47, 0x65, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c,
|
||||
0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x66, 0x0a, 0x19, 0x47, 0x65, 0x74,
|
||||
0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x10, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1f, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f,
|
||||
0x67, 0x52, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f,
|
||||
0x67, 0x22, 0x1c, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
|
||||
0x6a, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b,
|
||||
0x0a, 0x11, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6c,
|
||||
0x6f, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x65, 0x74, 0x74,
|
||||
0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x0f, 0x68, 0x74, 0x74, 0x70,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x22, 0x58, 0x0a, 0x11, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72,
|
||||
0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x63, 0x6f, 0x70,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x6e, 0x6c, 0x79, 0x49, 0x6e, 0x53,
|
||||
0x63, 0x6f, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x65,
|
||||
0x78, 0x70, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x72, 0x63,
|
||||
0x68, 0x45, 0x78, 0x70, 0x72, 0x22, 0x1d, 0x0a, 0x1b, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x74,
|
||||
0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x22, 0x1e, 0x0a, 0x1c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x74, 0x74,
|
||||
0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x32, 0xf0, 0x02, 0x0a, 0x15, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6c,
|
||||
0x0a, 0x11, 0x47, 0x65, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x4c, 0x6f, 0x67, 0x12, 0x29, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c,
|
||||
0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a,
|
||||
0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x47, 0x65, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c,
|
||||
0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x72, 0x0a, 0x13,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c,
|
||||
0x6f, 0x67, 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c,
|
||||
0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x2c, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x75, 0x0a, 0x14, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x2c, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79,
|
||||
0x2e, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72,
|
||||
0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x68, 0x65, 0x74, 0x74, 0x79, 0x2e, 0x72,
|
||||
0x65, 0x71, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x74,
|
||||
0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x73, 0x74, 0x6f, 0x74, 0x69, 0x6a, 0x6e, 0x2f, 0x68,
|
||||
0x65, 0x74, 0x74, 0x79, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x65, 0x71, 0x6c, 0x6f, 0x67, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_reqlog_reqlog_proto_rawDescOnce sync.Once
|
||||
file_reqlog_reqlog_proto_rawDescData = file_reqlog_reqlog_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_reqlog_reqlog_proto_rawDescGZIP() []byte {
|
||||
file_reqlog_reqlog_proto_rawDescOnce.Do(func() {
|
||||
file_reqlog_reqlog_proto_rawDescData = protoimpl.X.CompressGZIP(file_reqlog_reqlog_proto_rawDescData)
|
||||
})
|
||||
return file_reqlog_reqlog_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_reqlog_reqlog_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_reqlog_reqlog_proto_goTypes = []any{
|
||||
(*HttpRequestLog)(nil), // 0: hetty.reqlog.v1.HttpRequestLog
|
||||
(*GetHttpRequestLogRequest)(nil), // 1: hetty.reqlog.v1.GetHttpRequestLogRequest
|
||||
(*GetHttpRequestLogResponse)(nil), // 2: hetty.reqlog.v1.GetHttpRequestLogResponse
|
||||
(*ListHttpRequestLogsRequest)(nil), // 3: hetty.reqlog.v1.ListHttpRequestLogsRequest
|
||||
(*ListHttpRequestLogsResponse)(nil), // 4: hetty.reqlog.v1.ListHttpRequestLogsResponse
|
||||
(*RequestLogsFilter)(nil), // 5: hetty.reqlog.v1.RequestLogsFilter
|
||||
(*ClearHttpRequestLogsRequest)(nil), // 6: hetty.reqlog.v1.ClearHttpRequestLogsRequest
|
||||
(*ClearHttpRequestLogsResponse)(nil), // 7: hetty.reqlog.v1.ClearHttpRequestLogsResponse
|
||||
(*http.Request)(nil), // 8: hetty.http.v1.Request
|
||||
(*http.Response)(nil), // 9: hetty.http.v1.Response
|
||||
}
|
||||
var file_reqlog_reqlog_proto_depIdxs = []int32{
|
||||
8, // 0: hetty.reqlog.v1.HttpRequestLog.request:type_name -> hetty.http.v1.Request
|
||||
9, // 1: hetty.reqlog.v1.HttpRequestLog.response:type_name -> hetty.http.v1.Response
|
||||
0, // 2: hetty.reqlog.v1.GetHttpRequestLogResponse.http_request_log:type_name -> hetty.reqlog.v1.HttpRequestLog
|
||||
0, // 3: hetty.reqlog.v1.ListHttpRequestLogsResponse.http_request_logs:type_name -> hetty.reqlog.v1.HttpRequestLog
|
||||
1, // 4: hetty.reqlog.v1.HttpRequestLogService.GetHttpRequestLog:input_type -> hetty.reqlog.v1.GetHttpRequestLogRequest
|
||||
3, // 5: hetty.reqlog.v1.HttpRequestLogService.ListHttpRequestLogs:input_type -> hetty.reqlog.v1.ListHttpRequestLogsRequest
|
||||
6, // 6: hetty.reqlog.v1.HttpRequestLogService.ClearHttpRequestLogs:input_type -> hetty.reqlog.v1.ClearHttpRequestLogsRequest
|
||||
2, // 7: hetty.reqlog.v1.HttpRequestLogService.GetHttpRequestLog:output_type -> hetty.reqlog.v1.GetHttpRequestLogResponse
|
||||
4, // 8: hetty.reqlog.v1.HttpRequestLogService.ListHttpRequestLogs:output_type -> hetty.reqlog.v1.ListHttpRequestLogsResponse
|
||||
7, // 9: hetty.reqlog.v1.HttpRequestLogService.ClearHttpRequestLogs:output_type -> hetty.reqlog.v1.ClearHttpRequestLogsResponse
|
||||
7, // [7:10] is the sub-list for method output_type
|
||||
4, // [4:7] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_reqlog_reqlog_proto_init() }
|
||||
func file_reqlog_reqlog_proto_init() {
|
||||
if File_reqlog_reqlog_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_reqlog_reqlog_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_reqlog_reqlog_proto_goTypes,
|
||||
DependencyIndexes: file_reqlog_reqlog_proto_depIdxs,
|
||||
MessageInfos: file_reqlog_reqlog_proto_msgTypes,
|
||||
}.Build()
|
||||
File_reqlog_reqlog_proto = out.File
|
||||
file_reqlog_reqlog_proto_rawDesc = nil
|
||||
file_reqlog_reqlog_proto_goTypes = nil
|
||||
file_reqlog_reqlog_proto_depIdxs = nil
|
||||
}
|
@ -3,27 +3,24 @@ package reqlog_test
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"go.etcd.io/bbolt"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/db/bolt"
|
||||
httppb "github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/testutil"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
var ulidEntropy = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
//nolint:paralleltest
|
||||
func TestRequestModifier(t *testing.T) {
|
||||
path := t.TempDir() + "bolt.db"
|
||||
@ -39,9 +36,9 @@ func TestRequestModifier(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
projectID := ulid.Make().String()
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
@ -58,34 +55,39 @@ func TestRequestModifier(t *testing.T) {
|
||||
}
|
||||
reqModFn := svc.RequestModifier(next)
|
||||
req := httptest.NewRequest("GET", "https://example.com/", strings.NewReader("bar"))
|
||||
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
req.Header.Add("X-Yolo", "swag")
|
||||
reqID := ulid.Make()
|
||||
req = req.WithContext(proxy.WithRequestID(req.Context(), reqID))
|
||||
|
||||
reqModFn(req)
|
||||
|
||||
t.Run("request log was stored in repository", func(t *testing.T) {
|
||||
exp := reqlog.RequestLog{
|
||||
ID: reqID,
|
||||
ProjectID: svc.ActiveProjectID(),
|
||||
Method: req.Method,
|
||||
URL: req.URL,
|
||||
Proto: req.Proto,
|
||||
Header: req.Header,
|
||||
Body: []byte("modified body"),
|
||||
exp := &reqlog.HttpRequestLog{
|
||||
Id: reqID.String(),
|
||||
ProjectId: svc.ActiveProjectID(),
|
||||
Request: &httppb.Request{
|
||||
Url: "https://example.com/",
|
||||
Method: httppb.Method_METHOD_GET,
|
||||
Protocol: httppb.Protocol_PROTOCOL_HTTP11,
|
||||
Headers: []*httppb.Header{
|
||||
{
|
||||
Key: "X-Yolo",
|
||||
Value: "swag",
|
||||
},
|
||||
},
|
||||
Body: []byte("modified body"),
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.FindRequestLogByID(context.Background(), reqID)
|
||||
got, err := db.FindRequestLogByID(context.Background(), svc.ActiveProjectID(), reqID.String())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find request by id: %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(exp, got); diff != "" {
|
||||
t.Fatalf("request log not equal (-exp, +got):\n%v", diff)
|
||||
}
|
||||
testutil.ProtoDiff(t, "request log not equal", exp, got)
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:paralleltest
|
||||
func TestResponseModifier(t *testing.T) {
|
||||
path := t.TempDir() + "bolt.db"
|
||||
boltDB, err := bbolt.Open(path, 0o600, nil)
|
||||
@ -100,9 +102,9 @@ func TestResponseModifier(t *testing.T) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
projectID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
err = db.UpsertProject(context.Background(), proj.Project{
|
||||
ID: projectID,
|
||||
projectID := "foobar-project-id"
|
||||
err = db.UpsertProject(context.Background(), &proj.Project{
|
||||
Id: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error upserting project: %v", err)
|
||||
@ -120,39 +122,44 @@ func TestResponseModifier(t *testing.T) {
|
||||
resModFn := svc.ResponseModifier(next)
|
||||
|
||||
req := httptest.NewRequest("GET", "https://example.com/", strings.NewReader("bar"))
|
||||
reqLogID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
|
||||
reqLogID := ulid.Make()
|
||||
req = req.WithContext(context.WithValue(req.Context(), reqlog.ReqLogIDKey, reqLogID))
|
||||
|
||||
err = db.StoreRequestLog(context.Background(), reqlog.RequestLog{
|
||||
ID: reqLogID,
|
||||
ProjectID: projectID,
|
||||
err = db.StoreRequestLog(context.Background(), &reqlog.HttpRequestLog{
|
||||
Id: reqLogID.String(),
|
||||
ProjectId: projectID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to store request log: %v", err)
|
||||
}
|
||||
|
||||
res := &http.Response{
|
||||
Request: req,
|
||||
Body: io.NopCloser(strings.NewReader("bar")),
|
||||
Request: req,
|
||||
Proto: "HTTP/1.1",
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(strings.NewReader("bar")),
|
||||
}
|
||||
|
||||
if err := resModFn(res); err != nil {
|
||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
||||
}
|
||||
|
||||
t.Run("request log was stored in repository", func(t *testing.T) {
|
||||
// Dirty (but simple) wait for other goroutine to finish calling repository.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// Dirty (but simple) wait for other goroutine to finish calling repository.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
got, err := svc.FindRequestLogByID(context.Background(), reqLogID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find request by id: %v", err)
|
||||
}
|
||||
got, err := db.FindRequestLogByID(context.Background(), svc.ActiveProjectID(), reqLogID.String())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find request by id: %v", err)
|
||||
}
|
||||
|
||||
t.Run("ran next modifier first, before calling repository", func(t *testing.T) {
|
||||
if exp := "modified body"; exp != string(got.Response.Body) {
|
||||
t.Fatalf("incorrect `ResponseLog.Body` value (expected: %v, got: %v)", exp, string(got.Response.Body))
|
||||
}
|
||||
})
|
||||
})
|
||||
exp := &httppb.Response{
|
||||
Protocol: httppb.Protocol_PROTOCOL_HTTP11,
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Headers: []*httppb.Header{},
|
||||
Body: []byte("modified body"),
|
||||
}
|
||||
|
||||
testutil.ProtoDiff(t, "response not equal", exp, got.GetResponse())
|
||||
}
|
||||
|
@ -3,40 +3,34 @@ package reqlog
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/oklog/ulid/v2"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
)
|
||||
|
||||
var reqLogSearchKeyFns = map[string]func(rl RequestLog) string{
|
||||
"req.id": func(rl RequestLog) string { return rl.ID.String() },
|
||||
"req.proto": func(rl RequestLog) string { return rl.Proto },
|
||||
"req.url": func(rl RequestLog) string {
|
||||
if rl.URL == nil {
|
||||
var reqLogSearchKeyFns = map[string]func(rl *HttpRequestLog) string{
|
||||
"req.id": func(rl *HttpRequestLog) string { return rl.GetId() },
|
||||
"req.proto": func(rl *HttpRequestLog) string { return rl.GetRequest().GetProtocol().String() },
|
||||
"req.url": func(rl *HttpRequestLog) string { return rl.GetRequest().GetUrl() },
|
||||
"req.method": func(rl *HttpRequestLog) string { return rl.GetRequest().GetMethod().String() },
|
||||
"req.body": func(rl *HttpRequestLog) string { return string(rl.GetRequest().GetBody()) },
|
||||
"req.timestamp": func(rl *HttpRequestLog) string {
|
||||
id, err := ulid.Parse(rl.GetId())
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return rl.URL.String()
|
||||
return ulid.Time(id.Time()).String()
|
||||
},
|
||||
"req.method": func(rl RequestLog) string { return rl.Method },
|
||||
"req.body": func(rl RequestLog) string { return string(rl.Body) },
|
||||
"req.timestamp": func(rl RequestLog) string { return ulid.Time(rl.ID.Time()).String() },
|
||||
}
|
||||
|
||||
var ResLogSearchKeyFns = map[string]func(rl ResponseLog) string{
|
||||
"res.proto": func(rl ResponseLog) string { return rl.Proto },
|
||||
"res.statusCode": func(rl ResponseLog) string { return strconv.Itoa(rl.StatusCode) },
|
||||
"res.statusReason": func(rl ResponseLog) string { return rl.Status },
|
||||
"res.body": func(rl ResponseLog) string { return string(rl.Body) },
|
||||
}
|
||||
|
||||
// TODO: Request and response headers search key functions.
|
||||
|
||||
// Matches returns true if the supplied search expression evaluates to true.
|
||||
func (reqLog RequestLog) Matches(expr filter.Expression) (bool, error) {
|
||||
func (reqLog *HttpRequestLog) Matches(expr filter.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case filter.PrefixExpression:
|
||||
return reqLog.matchPrefixExpr(e)
|
||||
@ -49,7 +43,7 @@ func (reqLog RequestLog) Matches(expr filter.Expression) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
func (reqLog *HttpRequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpNot:
|
||||
match, err := reqLog.Matches(expr.Right)
|
||||
@ -63,7 +57,7 @@ func (reqLog RequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, er
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
func (reqLog *HttpRequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpAnd:
|
||||
left, err := reqLog.Matches(expr.Left)
|
||||
@ -99,7 +93,7 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
leftVal := reqLog.getMappedStringLiteral(left.Value)
|
||||
|
||||
if leftVal == "req.headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Header)
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Request.Headers)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
@ -108,7 +102,7 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
}
|
||||
|
||||
if leftVal == "res.headers" && reqLog.Response != nil {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Header)
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Headers)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
|
||||
}
|
||||
@ -159,7 +153,7 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) getMappedStringLiteral(s string) string {
|
||||
func (reqLog *HttpRequestLog) getMappedStringLiteral(s string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "req."):
|
||||
fn, ok := reqLogSearchKeyFns[s]
|
||||
@ -167,28 +161,22 @@ func (reqLog RequestLog) getMappedStringLiteral(s string) string {
|
||||
return fn(reqLog)
|
||||
}
|
||||
case strings.HasPrefix(s, "res."):
|
||||
if reqLog.Response == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fn, ok := ResLogSearchKeyFns[s]
|
||||
fn, ok := http.ResponseSearchKeyFns[s]
|
||||
if ok {
|
||||
return fn(*reqLog.Response)
|
||||
return fn(reqLog.GetResponse())
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
|
||||
for key, values := range reqLog.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
func (reqLog *HttpRequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
|
||||
for _, header := range reqLog.GetRequest().GetHeaders() {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", header.Key, header.Value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,21 +189,19 @@ func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bo
|
||||
}
|
||||
}
|
||||
|
||||
if reqLog.Response != nil {
|
||||
for key, values := range reqLog.Response.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
if res := reqLog.GetResponse(); res != nil {
|
||||
for _, header := range res.Headers {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", header.Key, header.Value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range ResLogSearchKeyFns {
|
||||
for _, fn := range http.ResponseSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(*reqLog.Response)),
|
||||
strings.ToLower(fn(reqLog.GetResponse())),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
@ -226,29 +212,25 @@ func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bo
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) MatchScope(s *scope.Scope) bool {
|
||||
func (reqLog *HttpRequestLog) MatchScope(s *scope.Scope) bool {
|
||||
for _, rule := range s.Rules() {
|
||||
if rule.URL != nil && reqLog.URL != nil {
|
||||
if matches := rule.URL.MatchString(reqLog.URL.String()); matches {
|
||||
if rule.URL != nil {
|
||||
if matches := rule.URL.MatchString(reqLog.GetRequest().GetUrl()); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range reqLog.Header {
|
||||
for _, header := range reqLog.GetRequest().GetHeaders() {
|
||||
var keyMatches, valueMatches bool
|
||||
|
||||
if rule.Header.Key != nil {
|
||||
if matches := rule.Header.Key.MatchString(key); matches {
|
||||
keyMatches = true
|
||||
}
|
||||
if matches := rule.Header.Key.MatchString(header.Key); matches {
|
||||
keyMatches = true
|
||||
}
|
||||
|
||||
if rule.Header.Value != nil {
|
||||
for _, value := range values {
|
||||
if matches := rule.Header.Value.MatchString(value); matches {
|
||||
valueMatches = true
|
||||
break
|
||||
}
|
||||
if matches := rule.Header.Value.MatchString(header.Value); matches {
|
||||
valueMatches = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// When only key or value is set, match on whatever is set.
|
||||
@ -264,7 +246,7 @@ func (reqLog RequestLog) MatchScope(s *scope.Scope) bool {
|
||||
}
|
||||
|
||||
if rule.Body != nil {
|
||||
if matches := rule.Body.Match(reqLog.Body); matches {
|
||||
if matches := rule.Body.Match(reqLog.GetRequest().GetBody()); matches {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/http"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
)
|
||||
|
||||
@ -13,15 +14,17 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
requestLog reqlog.RequestLog
|
||||
requestLog *reqlog.HttpRequestLog
|
||||
expectedMatch bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "infix expression, equal operator, match",
|
||||
query: "req.body = foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -29,8 +32,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, not equal operator, match",
|
||||
query: "req.body != bar",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -38,8 +43,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than operator, match",
|
||||
query: "req.body > a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -47,8 +54,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than operator, match",
|
||||
query: "req.body < b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -56,8 +65,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match greater than",
|
||||
query: "req.body >= a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -65,8 +76,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, greater than or equal operator, match equal",
|
||||
query: "req.body >= a",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -74,8 +87,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match less than",
|
||||
query: "req.body <= b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("a"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("a"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -83,8 +98,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, less than or equal operator, match equal",
|
||||
query: "req.body <= b",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("b"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("b"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -92,8 +109,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, regular expression operator, match",
|
||||
query: `req.body =~ "^foo(.*)$"`,
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foobar"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foobar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -101,8 +120,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, negate regular expression operator, match",
|
||||
query: `req.body !~ "^foo(.*)$"`,
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("xoobar"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("xoobar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -110,9 +131,11 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, and operator, match",
|
||||
query: "req.body = bar AND res.body = yolo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("bar"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
Response: &http.Response{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
@ -122,9 +145,11 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "infix expression, or operator, match",
|
||||
query: "req.body = bar OR res.body = yolo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
Response: &reqlog.ResponseLog{
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
Response: &http.Response{
|
||||
Body: []byte("yolo"),
|
||||
},
|
||||
},
|
||||
@ -134,8 +159,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "prefix expression, not operator, match",
|
||||
query: "NOT (req.body = bar)",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -143,8 +170,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, match in request log",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("foo"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
expectedMatch: true,
|
||||
expectedError: nil,
|
||||
@ -152,8 +181,10 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, no match",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Body: []byte("bar"),
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Request: &http.Request{
|
||||
Body: []byte("bar"),
|
||||
},
|
||||
},
|
||||
expectedMatch: false,
|
||||
expectedError: nil,
|
||||
@ -161,8 +192,8 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
{
|
||||
name: "string literal expression, match in response log",
|
||||
query: "foo",
|
||||
requestLog: reqlog.RequestLog{
|
||||
Response: &reqlog.ResponseLog{
|
||||
requestLog: &reqlog.HttpRequestLog{
|
||||
Response: &http.Response{
|
||||
Body: []byte("foo"),
|
||||
},
|
||||
},
|
||||
|
Reference in New Issue
Block a user