99 "net/http"
1010 "sort"
1111 "strconv"
12+ "time"
1213
14+ "github.com/TykTechnologies/tyk-pump/analytics"
1315 "github.com/TykTechnologies/tyk/apidef/oas"
1416 "github.com/TykTechnologies/tyk/common/option"
1517 "github.com/TykTechnologies/tyk/header"
@@ -21,14 +23,22 @@ var _ TykMiddleware = (*mockResponseMiddleware)(nil)
2123
2224type mockResponseMiddleware struct {
2325 * BaseMiddleware
26+ hitRecorder hitRecorder
2427}
2528
2629func newMockResponseMiddleware (base * BaseMiddleware , opts ... option.Option [mockResponseMiddleware ]) TykMiddleware {
2730 return option .New (opts ).Build (mockResponseMiddleware {
2831 BaseMiddleware : base ,
32+ hitRecorder : & realHitRecorder {successHandler : & SuccessHandler {base .Copy ()}},
2933 })
3034}
3135
36+ func withHitRecorder (h hitRecorder ) option.Option [mockResponseMiddleware ] {
37+ return func (m * mockResponseMiddleware ) {
38+ m .hitRecorder = h
39+ }
40+ }
41+
3242func (m * mockResponseMiddleware ) Name () string {
3343 return "MockResponseMiddleware"
3444}
@@ -61,11 +71,13 @@ func (m *mockResponseMiddleware) forward(res *http.Response, rw http.ResponseWri
6171}
6272
6373func (m * mockResponseMiddleware ) ProcessRequest (rw http.ResponseWriter , r * http.Request , _ interface {}) (error , int ) {
74+ start := time .Now ()
75+
6476 if ! m .Spec .hasActiveMock () {
6577 return nil , http .StatusOK
6678 }
6779
68- res , err := m .mockResponse (r )
80+ res , requestOverwritten , err := m .mockResponse (r )
6981
7082 if err != nil {
7183 return fmt .Errorf ("failed to mock response: %w" , err ), http .StatusInternalServerError
@@ -86,10 +98,16 @@ func (m *mockResponseMiddleware) ProcessRequest(rw http.ResponseWriter, r *http.
8698 return fmt .Errorf ("failed to forward response: %w" , err ), http .StatusInternalServerError
8799 }
88100
101+ m .hitRecorder .hit (rw , requestOverwritten , res , start )
102+
89103 return nil , middleware .StatusRespond
90104}
91105
92- func (m * mockResponseMiddleware ) mockResponse (r * http.Request ) (* http.Response , error ) {
106+ func (m * mockResponseMiddleware ) mockResponse (r * http.Request ) (
107+ res * http.Response ,
108+ internal * http.Request ,
109+ err error ,
110+ ) {
93111 // Use FindSpecMatchesStatus to check if this path should be mocked
94112 // This ensures the standard regex-based path matching is used, respecting gateway configurations
95113 versionInfo , _ := m .Spec .Version (r )
@@ -99,21 +117,23 @@ func (m *mockResponseMiddleware) mockResponse(r *http.Request) (*http.Response,
99117
100118 if ! found || urlSpec == nil {
101119 // No mock response configured for this path
102- return nil , nil
120+ return nil , nil , nil
103121 }
104122
105123 mockResponse := urlSpec .OASMockResponseMeta
106124 if mockResponse == nil || ! mockResponse .Enabled {
107- return nil , nil
125+ return nil , nil , nil
108126 }
109127
110- res := & http.Response {Header : http.Header {}}
128+ res = & http.Response {Header : http.Header {}}
129+
130+ internal = r .Clone (r .Context ())
131+ internal .URL .Path = urlSpec .OASPath
111132
112133 var code int
113134 var contentType string
114135 var body []byte
115136 var headers []oas.Header
116- var err error
117137
118138 if mockResponse .FromOASExamples != nil && mockResponse .FromOASExamples .Enabled {
119139 // Find the route using the OAS path from URLSpec, not the actual request path.
@@ -122,13 +142,12 @@ func (m *mockResponseMiddleware) mockResponse(r *http.Request) (*http.Response,
122142 route , _ , routeErr := m .Spec .findRouteForOASPath (urlSpec .OASPath , urlSpec .OASMethod , strippedPath , r .URL .Path )
123143 if routeErr != nil || route == nil {
124144 log .Tracef ("URL spec matched for mock response but route not found for OAS path %s: %v" , urlSpec .OASPath , routeErr )
125- return nil , nil
145+ return nil , nil , nil
126146 }
127147 code , contentType , body , headers , err = mockFromOAS (r , route .Operation , mockResponse .FromOASExamples )
128148 res .StatusCode = code
129149 if err != nil {
130- err = fmt .Errorf ("mock: %w" , err )
131- return res , err
150+ return res , internal , fmt .Errorf ("mock: %w" , err )
132151 }
133152 } else {
134153 code , body , headers = mockFromConfig (mockResponse )
@@ -143,12 +162,15 @@ func (m *mockResponseMiddleware) mockResponse(r *http.Request) (*http.Response,
143162 }
144163
145164 res .StatusCode = code
165+ res .Body = nopCloser {ReadSeeker : bytes .NewReader (body )}
146166
147- res .Body = io .NopCloser (bytes .NewBuffer (body ))
167+ if m .Gw .GetConfig ().CloseConnections {
168+ res .Header .Set (header .Connection , "close" )
169+ }
148170
149171 m .Spec .sendRateLimitHeaders (ctxGetSession (r ), res )
150172
151- return res , nil
173+ return res , internal , nil
152174}
153175
154176func mockFromConfig (tykMockRespOp * oas.MockResponse ) (int , []byte , []oas.Header ) {
@@ -268,3 +290,23 @@ func mockFromOAS(r *http.Request, operation *openapi3.Operation, fromOASExamples
268290
269291 return code , contentType , body , headers , err
270292}
293+
294+ type hitRecorder interface {
295+ hit (rw http.ResponseWriter , r * http.Request , res * http.Response , start time.Time )
296+ }
297+
298+ type realHitRecorder struct {
299+ successHandler * SuccessHandler
300+ }
301+
302+ func (s * realHitRecorder ) hit (rw http.ResponseWriter , r * http.Request , res * http.Response , start time.Time ) {
303+ if s .successHandler .Spec .DoNotTrack {
304+ return
305+ }
306+
307+ ms := DurationToMillisecond (time .Since (start ))
308+ latency := analytics.Latency {Total : int64 (ms ), Upstream : 0 , Gateway : int64 (ms )}
309+ s .successHandler .RecordHit (r , latency , res .StatusCode , res , true )
310+ s .successHandler .RecordAccessLog (r , res , latency )
311+ s .successHandler .Base ().RecordMetrics (rw , r , res .StatusCode , latency , res )
312+ }
0 commit comments