-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[Debugger MVP] Some middleware doesn't work for OAS debugger #7093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Debugger MVP] Some middleware doesn't work for OAS debugger #7093
Conversation
This PR is too huge for one to review 💔
Consider breaking it down into multiple small PRs. Check out this guide to learn more about PR best-practices. |
API Changes --- prev.txt 2025-06-06 07:48:50.795914854 +0000
+++ current.txt 2025-06-06 07:48:41.226960724 +0000
@@ -1986,6 +1986,11 @@
CONSTANTS
const (
+
+ // UpstreamUrlDefault default upstream url for OAS
+ UpstreamUrlDefault = "http://localhost:3478/"
+)
+const (
WebhookKind = event.WebhookKind
JSVMKind = event.JSVMKind
LogKind = event.LogKind
@@ -2022,6 +2027,10 @@
VARIABLES
var (
+ ErrMinRateLimitExceeded = errors.New("minimum rate limit exceeded")
+ ErrZeroAmountInRateLimit = errors.New("zero amount in rate limit")
+)
+var (
// URLRewriteConditions contains all valid URL rewrite condition values.
URLRewriteConditions = []URLRewriteCondition{
ConditionAll,
@@ -2282,6 +2291,43 @@
Fill updates the BatchProcessing configuration based on the
EnableBatchRequestSupport value from the given APIDefinition.
+type Builder struct {
+ // Has unexported fields.
+}
+ Builder OAS builder is responsible for providing methods for building valid
+ OAS object.
+
+func NewBuilder() Builder
+
+func (b *Builder) Build() (*OAS, error)
+
+type BuilderOption = option.Option[Builder]
+ BuilderOption optional parameter should be used to define builder options in
+ declarative way.
+
+func WithDelete(path string, fn EndpointFactory) BuilderOption
+ WithDelete add delete endpoint/path to OAS
+
+func WithGet(path string, fn EndpointFactory) BuilderOption
+ WithGet add get endpoint/path to OAS
+
+func WithGlobalRateLimit(rate uint, duration time.Duration, enabled ...bool) BuilderOption
+ WithGlobalRateLimit defines global rate limit
+
+func WithListenPath(path string, strip bool) BuilderOption
+
+func WithPost(path string, fn EndpointFactory) BuilderOption
+ WithPost add post endpoint/path to OAS
+
+func WithPut(path string, fn EndpointFactory) BuilderOption
+ WithPut add put endpoint/path to OAS
+
+func WithTestDefaults() BuilderOption
+ WithTestDefaults sets defaults options to be sued for testing
+
+func WithUpstreamUrl(upstreamUrl string) BuilderOption
+ WithUpstreamUrl defines upstream url
+
type CORS struct {
// Enabled is a boolean flag, if set to `true`, this option enables CORS processing.
//
@@ -2714,6 +2760,19 @@
DomainToCertificate holds a single mapping of domain name into a
certificate.
+type EndpointBuilder struct {
+ // Has unexported fields.
+}
+ EndpointBuilder OAS endpoint builder should be used for building endpoint
+ and binding middlewares to it.
+
+func (eb *EndpointBuilder) Mock(fn func(mock *MockResponse)) *EndpointBuilder
+
+func (eb *EndpointBuilder) RateLimit(amount uint, duration time.Duration, enabled ...bool) *EndpointBuilder
+
+type EndpointFactory func(b *EndpointBuilder)
+ EndpointFactory factory method in which endpoint builder should be used.
+
type EndpointPostPlugin struct {
// Enabled activates post plugin.
//
@@ -3558,11 +3617,11 @@
func FillOASFromClassicAPIDefinition(api *apidef.APIDefinition, oas *OAS) (*OAS, error)
-func NewOAS() *OAS
- NewOAS returns an allocated *OAS.
-
func NewOASFromClassicAPIDefinition(api *apidef.APIDefinition) (*OAS, error)
+func NewOas(opts ...BuilderOption) (*OAS, error)
+ NewOas returns an allocated *OAS due to provided options
+
func (s *OAS) AddServers(apiURLs ...string)
AddServers adds a server into the servers definition if not already present.
@@ -5488,6 +5547,23 @@
func AddTo(app *kingpin.Application)
AddTo initializes a version info object.
+# Package: ./common/option
+
+package option // import "github.com/TykTechnologies/tyk/common/option"
+
+
+TYPES
+
+type FailableOption[O any] func(*O) error
+
+type Option[O any] func(*O)
+
+type Options[O any] []Option[O]
+
+func New[O any](opts []Option[O]) Options[O]
+
+func (o Options[O]) Build(baseVal O) *O
+
# Package: ./config
package config // import "github.com/TykTechnologies/tyk/config"
@@ -8697,6 +8773,9 @@
events.
func UpdateAPIVersion(spec *APISpec, name string, verGen func(version *apidef.VersionInfo))
+func WithQuotaKey(key string) option.Option[ProcessSpecOptions]
+ WithQuotaKey overrides quota key manually
+
func WrappedCharsetReader(s string, i io.Reader) (io.Reader, error)
TYPES
@@ -8783,9 +8862,7 @@
GraphEngine graphengine.Engine
- HasMock bool
- HasValidateRequest bool
- OASRouter routers.Router
+ OASRouter routers.Router
// Has unexported fields.
}
APISpec represents a path specification for an API, to avoid enumerating
@@ -8828,6 +8905,8 @@
func (a *APISpec) SanitizeProxyPaths(r *http.Request)
+func (a *APISpec) SetupOperation(r *http.Request)
+
func (a *APISpec) StopSessionManagerPool()
func (a *APISpec) StripListenPath(reqPath string) string
@@ -8939,7 +9018,7 @@
func (t *BaseMiddleware) Config() (interface{}, error)
-func (m *BaseMiddleware) Copy() *BaseMiddleware
+func (t *BaseMiddleware) Copy() *BaseMiddleware
Copy provides a new BaseMiddleware with it's own logger scope (copy).
The Spec, Proxy and Gw values are not copied.
@@ -10501,6 +10580,11 @@
ApplyPolicies []string `json:"apply_policies"`
}
+type ProcessSpecOptions struct {
+ // Has unexported fields.
+}
+ ProcessSpecOptions represents options for processSpec method
+
type ProxyResponse struct {
Response *http.Response
// UpstreamLatency the time it takes to do roundtrip to upstream. Total time
@@ -10702,15 +10786,15 @@
// Has unexported fields.
}
- RateLimitAndQuotaCheck will check the incoming request and key whether
- it is within it's quota and within it's rate limit, it makes use of the
- SessionLimiter object to do this
+ RateLimitForAPI will check the incoming request and key whether it is within
+ it's quota and within it's rate limit, it makes use of the SessionLimiter
+ object to do this
func (k *RateLimitForAPI) EnabledForSpec() bool
func (k *RateLimitForAPI) Name() string
-func (k *RateLimitForAPI) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
+func (k *RateLimitForAPI) ProcessRequest(_ http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
ProcessRequest will run any checks on the request on the way through the
system, return an error to have the chain fail
@@ -11214,7 +11298,16 @@
func (l *SessionLimiter) Context() context.Context
-func (l *SessionLimiter) ForwardMessage(r *http.Request, session *user.SessionState, rateLimitKey string, quotaKey string, store storage.Handler, enableRL, enableQ bool, api *APISpec, dryRun bool) sessionFailReason
+func (l *SessionLimiter) ForwardMessage(
+ r *http.Request,
+ session *user.SessionState,
+ rateLimitKey string,
+ quotaKey string,
+ store storage.Handler,
+ enableRL, enableQ bool,
+ api *APISpec,
+ dryRun bool,
+) sessionFailReason
ForwardMessage will enforce rate limiting, returning a non-zero
sessionFailReason if session limits have been exceeded. Key values to manage
rate are Rate and Per, e.g. Rate of 10 messages Per 10 seconds
@@ -11668,7 +11761,7 @@
func (k *ValidateJSON) Name() string
-func (k *ValidateJSON) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
+func (k *ValidateJSON) ProcessRequest(_ http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
ProcessRequest will run any checks on the request on the way through the
system, return an error to have the chain fail
@@ -11855,21 +11948,23 @@
TextXML = "text/xml"
)
const (
- XRealIP = "X-Real-IP"
- XForwardFor = "X-Forwarded-For"
- XAuthResult = "X-Auth-Result"
- XSessionAlias = "X-Session-Alias"
- XInitialURI = "X-Initial-URI"
- XForwardProto = "X-Forwarded-Proto"
- XContentTypeOptions = "X-Content-Type-Options"
- XXSSProtection = "X-XSS-Protection"
- XFrameOptions = "X-Frame-Options"
- XTykNodeID = "x-tyk-nodeid"
- XTykSessionID = "x-tyk-session-id"
- XTykNonce = "x-tyk-nonce"
- XTykHostname = "x-tyk-hostname"
- XGenerator = "X-Generator"
- XTykAuthorization = "X-Tyk-Authorization"
+ XRealIP = "X-Real-IP"
+ XForwardFor = "X-Forwarded-For"
+ XAuthResult = "X-Auth-Result"
+ XSessionAlias = "X-Session-Alias"
+ XInitialURI = "X-Initial-URI"
+ XForwardProto = "X-Forwarded-Proto"
+ XContentTypeOptions = "X-Content-Type-Options"
+ XXSSProtection = "X-XSS-Protection"
+ XFrameOptions = "X-Frame-Options"
+ XTykNodeID = "x-tyk-nodeid"
+ XTykSessionID = "x-tyk-session-id"
+ XTykNonce = "x-tyk-nonce"
+ XTykHostname = "x-tyk-hostname"
+ XGenerator = "X-Generator"
+ XTykAuthorization = "X-Tyk-Authorization"
+ XTykAcceptExampleName = "X-Tyk-Accept-Example-Name"
+ XTykAcceptExampleCode = "X-Tyk-Accept-Example-Code"
)
const (
Upgrade = "Upgrade" |
…nt-work-for-oas-debugger
…nt-work-for-oas-debugger
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…nt-work-for-oas-debugger
lgtm |
|
/release to release-5.8 |
Working on it! Note that it can take a few minutes. |
<details open> <summary><a href="https://tyktech.atlassian.net/browse/TT-14156" title="TT-14156" target="_blank">TT-14156</a></summary> <br /> <table> <tr> <th>Summary</th> <td>[Debugger MVP] Some middleware doesn't work for OAS debugger</td> </tr> <tr> <th>Type</th> <td> <img alt="Bug" src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium" /> Bug </td> </tr> <tr> <th>Status</th> <td>In Code Review</td> </tr> <tr> <th>Points</th> <td>N/A</td> </tr> <tr> <th>Labels</th> <td><a href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20codilime_refined%20ORDER%20BY%20created%20DESC" title="codilime_refined">codilime_refined</a></td> </tr> </table> </details> <!-- do not remove this marker as it will break jira-lint's functionality. added_by_jira_lint --> --- <!-- Provide a general summary of your changes in the Title above --> <!-- Describe your changes in detail --> <!-- This project only accepts pull requests related to open issues. --> <!-- If suggesting a new feature or change, please discuss it in an issue first. --> <!-- If fixing a bug, there should be an issue describing it with steps to reproduce. --> <!-- OSS: Please link to the issue here. Tyk: please create/link the JIRA ticket. --> <!-- Why is this change required? What problem does it solve? --> <!-- Please describe in detail how you tested your changes --> <!-- Include details of your testing environment, and the tests --> <!-- you ran to see how your change affects other areas of the code, etc. --> <!-- This information is helpful for reviewers and QA. --> <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Refactoring or add test (improvements in base code or adds test coverage to functionality) <!-- Go over all the following points, and put an `x` in all the boxes that apply --> <!-- If there are no documentation updates required, mark the item as checked. --> <!-- Raise up any additional concerns not covered by the checklist. --> - [ ] I ensured that the documentation is up to date - [x] I explained why this PR updates go.mod in detail with reasoning why it's required - [ ] I would like a code coverage CI quality gate exception and have explained why 1. **Mock Middleware Bug Fix** The main issue with the Mock middleware was caused by an incorrect implementation of the (*traceRequest).toRequest method in gateway/tracing.go. This method was joining the API entity's ListenPath with the request path, which created incorrect URLs for mocked endpoints. This explains why it was working incorrectly before this changes. ```go // Previous problematic code in traceRequest.toRequest path, err := url.JoinPath( tr.Spec.Proxy.ListenPath, tr.Request.Path, ) ``` 2. **Rate Limit Middleware Bug Fix** The RateLimitForAPI middleware was using the LastUpdated field from the user.SessionState object to generate a unique key for rate limiting. When testing with the /trace/debug endpoint, this approach caused the rate limiter to use an inconsistent key, resulting in incorrect rate limiting behavior. Fix involves manually providing a stable key to the rate limiter factory by using ApiSpec.Checksum through the WithQuotaKey function: ```go chainObj := gw.processSpec( spec, nil, &gs, logrus.NewEntry(logger), WithQuotaKey(spec.Checksum), // Using Checksum as a stable key ) ``` This ensures consistent rate limiting behavior even for trace/debug endpoints. 3. Test Improvements Written three test cases with multiple assertions: - Tests for endpoints with global rate limiters - Tests for endpoints with per-endpoint rate limiters - Tests for the mock middleware functionality Additionally, I've created a builder/factory pattern to simplify the creation of valid OAS (OpenAPI Specification) objects for testing, which will make future test development easier and more maintainable. These changes collectively improve the reliability of the Mock middleware and Rate Limit middleware, especially when used with the trace/debug functionality. (cherry picked from commit d9954f9)
@shults Created merge PRs |
…or OAS debugger (#7093) (#7102) [Debugger MVP] Some middleware doesn't work for OAS debugger (#7093) <details open> <summary><a href="https://tyktech.atlassian.net/browse/TT-14156" title="TT-14156" target="_blank">TT-14156</a></summary> <br /> <table> <tr> <th>Summary</th> <td>[Debugger MVP] Some middleware doesn't work for OAS debugger</td> </tr> <tr> <th>Type</th> <td> <img alt="Bug" src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium" /> Bug </td> </tr> <tr> <th>Status</th> <td>In Code Review</td> </tr> <tr> <th>Points</th> <td>N/A</td> </tr> <tr> <th>Labels</th> <td><a href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20codilime_refined%20ORDER%20BY%20created%20DESC" title="codilime_refined">codilime_refined</a></td> </tr> </table> </details> <!-- do not remove this marker as it will break jira-lint's functionality. added_by_jira_lint --> --- <!-- Provide a general summary of your changes in the Title above --> ## Description <!-- Describe your changes in detail --> ## Related Issue <!-- This project only accepts pull requests related to open issues. --> <!-- If suggesting a new feature or change, please discuss it in an issue first. --> <!-- If fixing a bug, there should be an issue describing it with steps to reproduce. --> <!-- OSS: Please link to the issue here. Tyk: please create/link the JIRA ticket. --> ## Motivation and Context <!-- Why is this change required? What problem does it solve? --> ## How This Has Been Tested <!-- Please describe in detail how you tested your changes --> <!-- Include details of your testing environment, and the tests --> <!-- you ran to see how your change affects other areas of the code, etc. --> <!-- This information is helpful for reviewers and QA. --> ## Screenshots (if appropriate) ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Refactoring or add test (improvements in base code or adds test coverage to functionality) ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply --> <!-- If there are no documentation updates required, mark the item as checked. --> <!-- Raise up any additional concerns not covered by the checklist. --> - [ ] I ensured that the documentation is up to date - [x] I explained why this PR updates go.mod in detail with reasoning why it's required - [ ] I would like a code coverage CI quality gate exception and have explained why # Description of provided changes 1. **Mock Middleware Bug Fix** The main issue with the Mock middleware was caused by an incorrect implementation of the (*traceRequest).toRequest method in gateway/tracing.go. This method was joining the API entity's ListenPath with the request path, which created incorrect URLs for mocked endpoints. This explains why it was working incorrectly before this changes. ```go // Previous problematic code in traceRequest.toRequest path, err := url.JoinPath( tr.Spec.Proxy.ListenPath, tr.Request.Path, ) ``` 2. **Rate Limit Middleware Bug Fix** The RateLimitForAPI middleware was using the LastUpdated field from the user.SessionState object to generate a unique key for rate limiting. When testing with the /trace/debug endpoint, this approach caused the rate limiter to use an inconsistent key, resulting in incorrect rate limiting behavior. Fix involves manually providing a stable key to the rate limiter factory by using ApiSpec.Checksum through the WithQuotaKey function: ```go chainObj := gw.processSpec( spec, nil, &gs, logrus.NewEntry(logger), WithQuotaKey(spec.Checksum), // Using Checksum as a stable key ) ``` This ensures consistent rate limiting behavior even for trace/debug endpoints. 3. Test Improvements Written three test cases with multiple assertions: - Tests for endpoints with global rate limiters - Tests for endpoints with per-endpoint rate limiters - Tests for the mock middleware functionality Additionally, I've created a builder/factory pattern to simplify the creation of valid OAS (OpenAPI Specification) objects for testing, which will make future test development easier and more maintainable. These changes collectively improve the reliability of the Mock middleware and Rate Limit middleware, especially when used with the trace/debug functionality. Co-authored-by: Yaroslav Kotsur <[email protected]>
TT-14156
Description
Related Issue
Motivation and Context
How This Has Been Tested
Screenshots (if appropriate)
Types of changes
Checklist
Description of provided changes
The main issue with the Mock middleware was caused by an incorrect implementation of the (*traceRequest).toRequest method in gateway/tracing.go. This method was joining the API entity's ListenPath with the request path, which created incorrect URLs for mocked endpoints. This explains why it was working incorrectly before this changes.
The RateLimitForAPI middleware was using the LastUpdated field from the user.SessionState object to generate a unique key for rate limiting. When testing with the /trace/debug endpoint, this approach caused the rate limiter to use an inconsistent key, resulting in incorrect rate limiting behavior.
Fix involves manually providing a stable key to the rate limiter factory by using ApiSpec.Checksum through the WithQuotaKey function:
This ensures consistent rate limiting behavior even for trace/debug endpoints.
Written three test cases with multiple assertions:
Additionally, I've created a builder/factory pattern to simplify the creation of valid OAS (OpenAPI Specification) objects for testing, which will make future test development easier and more maintainable.
These changes collectively improve the reliability of the Mock middleware and Rate Limit middleware, especially when used with the trace/debug functionality.