Finish first working version of reqlog

This commit is contained in:
David Stotijn
2020-09-19 01:27:55 +02:00
parent c5cf6ef867
commit 211c11be2b
10 changed files with 646 additions and 280 deletions

View File

@ -1,12 +1,9 @@
package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
@ -44,51 +41,15 @@ func main() {
log.Fatalf("[FATAL] Could not parse CA: %v", err)
}
reqLogStore := reqlog.NewRequestLogStore()
reqLogService := reqlog.NewService()
p, err := proxy.NewProxy(caCert, tlsCA.PrivateKey)
if err != nil {
log.Fatalf("[FATAL] Could not create Proxy: %v", err)
}
p.UseRequestModifier(func(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
return func(req *http.Request) {
next(req)
clone := req.Clone(req.Context())
var body []byte
if req.Body != nil {
// TODO: Use io.LimitReader.
body, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Printf("[ERROR] Could not read request body for logging: %v", err)
return
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
}
reqLogStore.AddRequest(*clone, body)
}
})
p.UseResponseModifier(func(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
return func(res *http.Response) error {
if err := next(res); err != nil {
return err
}
clone := *res
var body []byte
if res.Body != nil {
// TODO: Use io.LimitReader.
var err error
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("could not read response body: %v", err)
}
res.Body = ioutil.NopCloser(bytes.NewBuffer(body))
}
reqLogStore.AddResponse(clone, body)
return nil
}
})
p.UseRequestModifier(reqLogService.RequestModifier)
p.UseResponseModifier(reqLogService.ResponseModifier)
var adminHandler http.Handler
@ -116,7 +77,7 @@ func main() {
// GraphQL server.
adminRouter.Path("/api/playground").Handler(playground.Handler("GraphQL Playground", "/api/graphql"))
adminRouter.Path("/api/graphql").Handler(handler.NewDefaultServer(api.NewExecutableSchema(api.Config{Resolvers: &api.Resolver{
RequestLogStore: &reqLogStore,
RequestLogService: &reqLogService,
}})))
// Admin interface.
@ -131,6 +92,7 @@ func main() {
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){}, // Disable HTTP/2
}
log.Println("[INFO] Running server on :8080 ...")
err = s.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatalf("[FATAL] HTTP server closed: %v", err)

6
go.mod
View File

@ -3,8 +3,8 @@ module github.com/dstotijn/gurp
go 1.13
require (
github.com/99designs/gqlgen v0.11.1
github.com/gorilla/mux v1.7.3
github.com/vektah/gqlparser v1.2.0
github.com/99designs/gqlgen v0.11.3
github.com/google/uuid v1.1.2
github.com/gorilla/mux v1.7.4
github.com/vektah/gqlparser/v2 v2.0.1
)

35
go.sum
View File

@ -1,24 +1,29 @@
github.com/99designs/gqlgen v0.10.2 h1:FfjCqIWejHDJeLpQTI0neoZo5vDO3sdo5oNCucet3A0=
github.com/99designs/gqlgen v0.10.2/go.mod h1:aDB7oabSAyZ4kUHLEySsLxnWrBy3lA0A2gWKU+qoHwI=
github.com/99designs/gqlgen v0.11.1 h1:QoSL8/AAJ2T3UOeQbdnBR32JcG4pO08+P/g5jdbFkUg=
github.com/99designs/gqlgen v0.11.1/go.mod h1:vjFOyBZ7NwDl+GdSD4PFn7BQn5Fy7ohJwXn7Vk8zz+c=
github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4=
github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
@ -43,23 +48,22 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser v1.2.0 h1:ntkSCX7F5ZJKl+HIVnmLaO269MruasVpNiMOjX9kgo0=
github.com/vektah/gqlparser v1.2.0/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -81,8 +85,7 @@ golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96b
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,6 +1,6 @@
@cert = $HOME/.ssh/gurp_cert.pem
@key = $HOME/.ssh/gurp_key.pem
@dev = false
@dev = true
@adminPath = $PWD/admin/build
**/*.go {

View File

@ -42,19 +42,26 @@ type DirectiveRoot struct {
}
type ComplexityRoot struct {
Query struct {
GetRequests func(childComplexity int) int
}
Request struct {
HTTPRequest struct {
Body func(childComplexity int) int
Method func(childComplexity int) int
Response func(childComplexity int) int
Timestamp func(childComplexity int) int
URL func(childComplexity int) int
}
HTTPResponse struct {
Body func(childComplexity int) int
StatusCode func(childComplexity int) int
}
Query struct {
GetHTTPRequests func(childComplexity int) int
}
}
type QueryResolver interface {
GetRequests(ctx context.Context) ([]Request, error)
GetHTTPRequests(ctx context.Context) ([]HTTPRequest, error)
}
type executableSchema struct {
@ -72,33 +79,61 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
_ = ec
switch typeName + "." + field {
case "Query.getRequests":
if e.complexity.Query.GetRequests == nil {
case "HttpRequest.body":
if e.complexity.HTTPRequest.Body == nil {
break
}
return e.complexity.Query.GetRequests(childComplexity), true
return e.complexity.HTTPRequest.Body(childComplexity), true
case "Request.method":
if e.complexity.Request.Method == nil {
case "HttpRequest.method":
if e.complexity.HTTPRequest.Method == nil {
break
}
return e.complexity.Request.Method(childComplexity), true
return e.complexity.HTTPRequest.Method(childComplexity), true
case "Request.timestamp":
if e.complexity.Request.Timestamp == nil {
case "HttpRequest.response":
if e.complexity.HTTPRequest.Response == nil {
break
}
return e.complexity.Request.Timestamp(childComplexity), true
return e.complexity.HTTPRequest.Response(childComplexity), true
case "Request.url":
if e.complexity.Request.URL == nil {
case "HttpRequest.timestamp":
if e.complexity.HTTPRequest.Timestamp == nil {
break
}
return e.complexity.Request.URL(childComplexity), true
return e.complexity.HTTPRequest.Timestamp(childComplexity), true
case "HttpRequest.url":
if e.complexity.HTTPRequest.URL == nil {
break
}
return e.complexity.HTTPRequest.URL(childComplexity), true
case "HttpResponse.body":
if e.complexity.HTTPResponse.Body == nil {
break
}
return e.complexity.HTTPResponse.Body(childComplexity), true
case "HttpResponse.statusCode":
if e.complexity.HTTPResponse.StatusCode == nil {
break
}
return e.complexity.HTTPResponse.StatusCode(childComplexity), true
case "Query.getHttpRequests":
if e.complexity.Query.GetHTTPRequests == nil {
break
}
return e.complexity.Query.GetHTTPRequests(childComplexity), true
}
return 0, false
@ -150,23 +185,37 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
}
var sources = []*ast.Source{
&ast.Source{Name: "pkg/api/schema.graphql", Input: `type Request {
&ast.Source{Name: "pkg/api/schema.graphql", Input: `type HttpRequest {
url: String!
method: HttpMethod!
body: String
timestamp: Time!
response: HttpResponse
}
type HttpResponse {
statusCode: Int!
body: String
}
type Query {
getRequests: [Request!]!
getHttpRequests: [HttpRequest!]!
}
enum HttpMethod {
GET
HEAD
POST
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
}
scalar Time`, BuiltIn: false},
scalar Time
`, BuiltIn: false},
}
var parsedSchema = gqlparser.MustLoadSchema(sources...)
@ -224,7 +273,236 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg
// region **************************** field.gotpl *****************************
func (ec *executionContext) _Query_getRequests(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
func (ec *executionContext) _HttpRequest_url(ctx context.Context, field graphql.CollectedField, obj *HTTPRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpRequest",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.URL, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpRequest_method(ctx context.Context, field graphql.CollectedField, obj *HTTPRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpRequest",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Method, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(HTTPMethod)
fc.Result = res
return ec.marshalNHttpMethod2githubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPMethod(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpRequest_body(ctx context.Context, field graphql.CollectedField, obj *HTTPRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpRequest",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Body, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpRequest_timestamp(ctx context.Context, field graphql.CollectedField, obj *HTTPRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpRequest",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Timestamp, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(time.Time)
fc.Result = res
return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpRequest_response(ctx context.Context, field graphql.CollectedField, obj *HTTPRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpRequest",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Response, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*HTTPResponse)
fc.Result = res
return ec.marshalOHttpResponse2ᚖgithubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpResponse_statusCode(ctx context.Context, field graphql.CollectedField, obj *HTTPResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpResponse",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.StatusCode, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
fc.Result = res
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpResponse_body(ctx context.Context, field graphql.CollectedField, obj *HTTPResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpResponse",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Body, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_getHttpRequests(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
@ -241,7 +519,7 @@ func (ec *executionContext) _Query_getRequests(ctx context.Context, field graphq
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().GetRequests(rctx)
return ec.resolvers.Query().GetHTTPRequests(rctx)
})
if err != nil {
ec.Error(ctx, err)
@ -253,9 +531,9 @@ func (ec *executionContext) _Query_getRequests(ctx context.Context, field graphq
}
return graphql.Null
}
res := resTmp.([]Request)
res := resTmp.([]HTTPRequest)
fc.Result = res
return ec.marshalNRequest2ᚕgithubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐRequestᚄ(ctx, field.Selections, res)
return ec.marshalNHttpRequest2ᚕgithubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPRequestᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -327,108 +605,6 @@ func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.C
return ec.marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx, field.Selections, res)
}
func (ec *executionContext) _Request_url(ctx context.Context, field graphql.CollectedField, obj *Request) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Request",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.URL, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Request_method(ctx context.Context, field graphql.CollectedField, obj *Request) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Request",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Method, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(HTTPMethod)
fc.Result = res
return ec.marshalNHttpMethod2githubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPMethod(ctx, field.Selections, res)
}
func (ec *executionContext) _Request_timestamp(ctx context.Context, field graphql.CollectedField, obj *Request) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Request",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Timestamp, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(time.Time)
fc.Result = res
return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -1492,6 +1668,76 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co
// region **************************** object.gotpl ****************************
var httpRequestImplementors = []string{"HttpRequest"}
func (ec *executionContext) _HttpRequest(ctx context.Context, sel ast.SelectionSet, obj *HTTPRequest) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, httpRequestImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("HttpRequest")
case "url":
out.Values[i] = ec._HttpRequest_url(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "method":
out.Values[i] = ec._HttpRequest_method(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "body":
out.Values[i] = ec._HttpRequest_body(ctx, field, obj)
case "timestamp":
out.Values[i] = ec._HttpRequest_timestamp(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "response":
out.Values[i] = ec._HttpRequest_response(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var httpResponseImplementors = []string{"HttpResponse"}
func (ec *executionContext) _HttpResponse(ctx context.Context, sel ast.SelectionSet, obj *HTTPResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, httpResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("HttpResponse")
case "statusCode":
out.Values[i] = ec._HttpResponse_statusCode(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "body":
out.Values[i] = ec._HttpResponse_body(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var queryImplementors = []string{"Query"}
func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
@ -1507,7 +1753,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("Query")
case "getRequests":
case "getHttpRequests":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
@ -1515,7 +1761,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_getRequests(ctx, field)
res = ec._Query_getHttpRequests(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
@ -1536,43 +1782,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
return out
}
var requestImplementors = []string{"Request"}
func (ec *executionContext) _Request(ctx context.Context, sel ast.SelectionSet, obj *Request) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, requestImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("Request")
case "url":
out.Values[i] = ec._Request_url(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "method":
out.Values[i] = ec._Request_method(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "timestamp":
out.Values[i] = ec._Request_timestamp(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var __DirectiveImplementors = []string{"__Directive"}
func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler {
@ -1841,11 +2050,11 @@ func (ec *executionContext) marshalNHttpMethod2githubᚗcomᚋdstotijnᚋgurpᚋ
return v
}
func (ec *executionContext) marshalNRequest2githubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐRequest(ctx context.Context, sel ast.SelectionSet, v Request) graphql.Marshaler {
return ec._Request(ctx, sel, &v)
func (ec *executionContext) marshalNHttpRequest2githubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPRequest(ctx context.Context, sel ast.SelectionSet, v HTTPRequest) graphql.Marshaler {
return ec._HttpRequest(ctx, sel, &v)
}
func (ec *executionContext) marshalNRequest2ᚕgithubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []Request) graphql.Marshaler {
func (ec *executionContext) marshalNHttpRequest2ᚕgithubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []HTTPRequest) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
@ -1869,7 +2078,7 @@ func (ec *executionContext) marshalNRequest2ᚕgithubᚗcomᚋdstotijnᚋgurpᚋ
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNRequest2githubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐRequest(ctx, sel, v[i])
ret[i] = ec.marshalNHttpRequest2githubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPRequest(ctx, sel, v[i])
}
if isLen1 {
f(i)
@ -1882,6 +2091,20 @@ func (ec *executionContext) marshalNRequest2ᚕgithubᚗcomᚋdstotijnᚋgurpᚋ
return ret
}
func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) {
return graphql.UnmarshalInt(v)
}
func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler {
res := graphql.MarshalInt(v)
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
}
return res
}
func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) {
return graphql.UnmarshalString(v)
}
@ -2159,6 +2382,17 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast
return ec.marshalOBoolean2bool(ctx, sel, *v)
}
func (ec *executionContext) marshalOHttpResponse2githubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPResponse(ctx context.Context, sel ast.SelectionSet, v HTTPResponse) graphql.Marshaler {
return ec._HttpResponse(ctx, sel, &v)
}
func (ec *executionContext) marshalOHttpResponse2ᚖgithubᚗcomᚋdstotijnᚋgurpᚋpkgᚋapiᚐHTTPResponse(ctx context.Context, sel ast.SelectionSet, v *HTTPResponse) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._HttpResponse(ctx, sel, v)
}
func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) {
return graphql.UnmarshalString(v)
}

View File

@ -9,27 +9,48 @@ import (
"time"
)
type Request struct {
URL string `json:"url"`
Method HTTPMethod `json:"method"`
Timestamp time.Time `json:"timestamp"`
type HTTPRequest struct {
URL string `json:"url"`
Method HTTPMethod `json:"method"`
Body *string `json:"body"`
Timestamp time.Time `json:"timestamp"`
Response *HTTPResponse `json:"response"`
}
type HTTPResponse struct {
StatusCode int `json:"statusCode"`
Body *string `json:"body"`
}
type HTTPMethod string
const (
HTTPMethodGet HTTPMethod = "GET"
HTTPMethodPost HTTPMethod = "POST"
HTTPMethodGet HTTPMethod = "GET"
HTTPMethodHead HTTPMethod = "HEAD"
HTTPMethodPost HTTPMethod = "POST"
HTTPMethodPut HTTPMethod = "PUT"
HTTPMethodDelete HTTPMethod = "DELETE"
HTTPMethodConnect HTTPMethod = "CONNECT"
HTTPMethodOptions HTTPMethod = "OPTIONS"
HTTPMethodTrace HTTPMethod = "TRACE"
HTTPMethodPatch HTTPMethod = "PATCH"
)
var AllHTTPMethod = []HTTPMethod{
HTTPMethodGet,
HTTPMethodHead,
HTTPMethodPost,
HTTPMethodPut,
HTTPMethodDelete,
HTTPMethodConnect,
HTTPMethodOptions,
HTTPMethodTrace,
HTTPMethodPatch,
}
func (e HTTPMethod) IsValid() bool {
switch e {
case HTTPMethodGet, HTTPMethodPost:
case HTTPMethodGet, HTTPMethodHead, HTTPMethodPost, HTTPMethodPut, HTTPMethodDelete, HTTPMethodConnect, HTTPMethodOptions, HTTPMethodTrace, HTTPMethodPatch:
return true
}
return false

View File

@ -1,5 +1,7 @@
package api
//go:generate go run github.com/99designs/gqlgen
import (
"context"
"fmt"
@ -8,27 +10,44 @@ import (
)
type Resolver struct {
RequestLogStore *reqlog.RequestLogStore
RequestLogService *reqlog.Service
}
type queryResolver struct{ *Resolver }
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
func (r *queryResolver) GetRequests(ctx context.Context) ([]Request, error) {
reqs := r.RequestLogStore.Requests()
resp := make([]Request, len(reqs))
func (r *queryResolver) GetHTTPRequests(ctx context.Context) ([]HTTPRequest, error) {
logs := r.RequestLogService.Requests()
reqs := make([]HTTPRequest, len(logs))
for i := range resp {
method := HTTPMethod(reqs[i].Request.Method)
for i, log := range logs {
method := HTTPMethod(log.Request.Method)
if !method.IsValid() {
return nil, fmt.Errorf("request has invalid method: %v", method)
}
resp[i] = Request{
URL: reqs[i].Request.URL.String(),
Method: method,
reqs[i] = HTTPRequest{
URL: log.Request.URL.String(),
Method: method,
Timestamp: log.Timestamp,
}
if len(log.Body) > 0 {
reqBody := string(log.Body)
reqs[i].Body = &reqBody
}
if log.Response != nil {
reqs[i].Response = &HTTPResponse{
StatusCode: log.Response.Response.StatusCode,
}
if len(log.Response.Body) > 0 {
resBody := string(log.Response.Body)
reqs[i].Response.Body = &resBody
}
}
}
return resp, nil
return reqs, nil
}

View File

@ -1,17 +1,30 @@
type Request {
type HttpRequest {
url: String!
method: HttpMethod!
body: String
timestamp: Time!
response: HttpResponse
}
type HttpResponse {
statusCode: Int!
body: String
}
type Query {
getRequests: [Request!]!
getHttpRequests: [HttpRequest!]!
}
enum HttpMethod {
GET
HEAD
POST
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
}
scalar Time
scalar Time

View File

@ -10,8 +10,14 @@ import (
"net"
"net/http"
"net/http/httputil"
"github.com/google/uuid"
)
type contextKey int
const ReqIDKey contextKey = 0
// Proxy implements http.Handler and offers MITM behaviour for modifying
// HTTP requests and responses.
type Proxy struct {
@ -46,6 +52,11 @@ func NewProxy(ca *x509.Certificate, key crypto.PrivateKey) (*Proxy, error) {
}
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Add a unique request ID, to be used for correlating responses to requests.
reqID := uuid.New()
ctx := context.WithValue(r.Context(), ReqIDKey, reqID)
r = r.WithContext(ctx)
if r.Method == http.MethodConnect {
p.handleConnect(w, r)
return
@ -69,6 +80,10 @@ func (p *Proxy) modifyRequest(r *http.Request) {
r.URL.Scheme = "https"
}
// Setting `X-Forwarded-For` to `nil` ensures that http.ReverseProxy doesn't
// set this header.
r.Header["X-Forwarded-For"] = nil
fn := nopReqModifier
for i := len(p.reqModifiers) - 1; i >= 0; i-- {
@ -119,7 +134,7 @@ func (p *Proxy) handleConnect(w http.ResponseWriter, r *http.Request) {
l := &OnceAcceptListener{clientConnNotify.Conn}
err = http.Serve(l, p.handler)
err = http.Serve(l, p)
if err != nil && err != ErrAlreadyAccepted {
log.Printf("[ERROR] Serving HTTP request failed: %v", err)
}

View File

@ -1,51 +1,150 @@
package reqlog
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"sync"
"time"
"github.com/dstotijn/gurp/pkg/proxy"
"github.com/google/uuid"
)
type Request struct {
Request http.Request
Body []byte
ID uuid.UUID
Request http.Request
Body []byte
Timestamp time.Time
Response *Response
}
type response struct {
res http.Response
body []byte
type Response struct {
Response http.Response
Body []byte
Timestamp time.Time
}
type RequestLogStore struct {
reqStore []Request
resStore []response
reqMu sync.Mutex
resMu sync.Mutex
type Service struct {
store []Request
mu sync.Mutex
}
func NewRequestLogStore() RequestLogStore {
return RequestLogStore{
reqStore: make([]Request, 0),
resStore: make([]response, 0),
func NewService() Service {
return Service{
store: make([]Request, 0),
}
}
func (store *RequestLogStore) AddRequest(req http.Request, body []byte) {
store.reqMu.Lock()
defer store.reqMu.Unlock()
func (svc *Service) Requests() []Request {
// TODO(?): Is locking necessary here?
svc.mu.Lock()
defer svc.mu.Unlock()
store.reqStore = append(store.reqStore, Request{req, body})
return svc.store
}
func (store *RequestLogStore) Requests() []Request {
store.reqMu.Lock()
defer store.reqMu.Unlock()
func (svc *Service) addRequest(reqID uuid.UUID, req http.Request, body []byte) Request {
svc.mu.Lock()
defer svc.mu.Unlock()
return store.reqStore
reqLog := Request{
ID: reqID,
Request: req,
Body: body,
Timestamp: time.Now(),
}
svc.store = append(svc.store, reqLog)
return reqLog
}
func (store *RequestLogStore) AddResponse(res http.Response, body []byte) {
store.resMu.Lock()
defer store.resMu.Unlock()
func (svc *Service) addResponse(reqID uuid.UUID, res http.Response, body []byte) error {
svc.mu.Lock()
defer svc.mu.Unlock()
store.resStore = append(store.resStore, response{res, body})
for i := range svc.store {
if svc.store[i].ID == reqID {
svc.store[i].Response = &Response{
Response: res,
Body: body,
Timestamp: time.Now(),
}
return nil
}
}
return fmt.Errorf("no request found with ID: %s", reqID)
}
func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
return func(req *http.Request) {
next(req)
clone := req.Clone(req.Context())
var body []byte
if req.Body != nil {
// TODO: Use io.LimitReader.
var err error
body, err = ioutil.ReadAll(req.Body)
if err != nil {
log.Printf("[ERROR] Could not read request body for logging: %v", err)
return
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
}
reqID, _ := req.Context().Value(proxy.ReqIDKey).(uuid.UUID)
if reqID == uuid.Nil {
log.Println("[ERROR] Request is missing a related request ID")
return
}
_ = svc.addRequest(reqID, *clone, body)
}
}
func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
return func(res *http.Response) error {
if err := next(res); err != nil {
return err
}
clone := *res
// TODO: Use io.LimitReader.
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("reqlog: could not read response body: %v", err)
}
res.Body = ioutil.NopCloser(bytes.NewBuffer(body))
if res.Header.Get("Content-Encoding") == "gzip" {
gzipReader, err := gzip.NewReader(bytes.NewBuffer(body))
if err != nil {
return fmt.Errorf("reqlog: could not create gzip reader: %v", err)
}
defer gzipReader.Close()
body, err = ioutil.ReadAll(gzipReader)
if err != nil {
return fmt.Errorf("reqlog: could not read gzipped response body: %v", err)
}
}
reqID, _ := res.Request.Context().Value(proxy.ReqIDKey).(uuid.UUID)
if reqID == uuid.Nil {
return errors.New("reqlog: request is missing ID")
}
if err := svc.addResponse(reqID, clone, body); err != nil {
return fmt.Errorf("reqlog: could not add response: %v", err)
}
return nil
}
}