Class RequestMatchers
- Author:
- jamesdbloom
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classCompact closest-match diagnostic used by the unmatched-404 hint header (closestMatchHintEnabled).Nested classes/interfaces inherited from class org.mockserver.mock.listeners.MockServerMatcherNotifier
MockServerMatcherNotifier.Cause -
Constructor Summary
ConstructorsConstructorDescriptionRequestMatchers(Configuration configuration, MockServerLogger mockServerLogger, Scheduler scheduler, WebSocketClientRegistry webSocketClientRegistry) -
Method Summary
Modifier and TypeMethodDescriptionadd(Expectation expectation, MockServerMatcherNotifier.Cause cause) voidRe-readmaxExpectationsfrom theConfigurationand resize the expectation store in place, so a change made viaPUT /mockserver/configurationactually takes effect instead of being accepted and ignored.voidclear(ExpectationId expectationId, String logCorrelationId) voidclear(RequestDefinition requestDefinition) voidclearByNamespace(String namespace, String logCorrelationId) Clears only the expectations belonging to the given namespace (tenant), leaving expectations in other namespaces (and global expectations) intact.findClosestMatchDiff(HttpRequest httpRequest) findClosestMatchHint(HttpRequest httpRequest) Find the expectation that came closest to matching the request (fewest field differences) together with its id and field diff.firstMatchingEarlyExpectation(HttpRequest headersOnlyRequest) firstMatchingExpectation(RequestDefinition requestDefinition) Returns the state backend, ornullif none has been set.booleanisEmpty()protected voidnotifyListeners(RequestMatchers notifier, MockServerMatcherNotifier.Cause cause) peekFirstMatchingExpectation(RequestDefinition requestDefinition) Side-effect-free probe: returns the first active expectation whose matcher matches the given request, WITHOUT consuming the match.postProcess(Expectation expectation) voidReconciles the node-local HttpRequestMatcher cache against the backend KeyValueStore.voidreset()voidretrieveActiveExpectations(RequestDefinition requestDefinition) retrieveExpectationsMatchingRequest(RequestDefinition requestDefinition) retrieveRequestDefinitions(List<ExpectationId> expectationIds) retrieveRequestMatchers(RequestDefinition requestDefinition) voidsetStateBackend(StateBackend stateBackend) Sets the state backend reference and wires the expectation KV store as the source of truth.intsize()voidupdate(Expectation[] expectations, MockServerMatcherNotifier.Cause cause) withCandidateIndexThreshold(int threshold) Sets the candidate-index engagement threshold on this instance without mutating any global/system state (so tests stay parallel-safe).Methods inherited from class org.mockserver.mock.listeners.MockServerMatcherNotifier
registerListener, unregisterListenerMethods inherited from class org.mockserver.model.ObjectWithReflectiveEqualsHashCodeToString
equals, fieldsExcludedFromEqualsAndHashCode, hashCode, toString
-
Constructor Details
-
RequestMatchers
public RequestMatchers(Configuration configuration, MockServerLogger mockServerLogger, Scheduler scheduler, WebSocketClientRegistry webSocketClientRegistry)
-
-
Method Details
-
withCandidateIndexThreshold
Sets the candidate-index engagement threshold on this instance without mutating any global/system state (so tests stay parallel-safe). Below this many expectationsfirstMatchingExpectation(org.mockserver.model.RequestDefinition)runs the untouched linear scan; at or above it the candidate index engages. Used by the matcher's own tests and the matching benchmark to exercise both paths deterministically; production code relies on the constructor-resolved value (defaultDEFAULT_CANDIDATE_INDEX_THRESHOLD, overridable via-Dmockserver.candidateIndexThreshold) and need not call this. Values below 2 are clamped to 2 (a 0/1-expectation store can never benefit). -
setStateBackend
Sets the state backend reference and wires the expectation KV store as the source of truth. Called byHttpStateafter construction. The node-local httpRequestMatchers CPQ becomes a derived cache; all mutations route through the backend first.When a backend is wired, the node-local CPQ's maxSize is raised to
Integer.MAX_VALUEso that eviction is controlled exclusively by the backend (avoiding insertion-order divergence between two CPQs on update-in-place vs re-insert).reconcileEvictions()trims the node-local cache after each backend mutation.Threading contract (issue #2579): control-plane mutations are NOT externally serialized —
PUT /mockserver/expectationadds run concurrently on thenioEventLoopThreadCountNetty worker threads (one per connection), and a clustered backend fires invalidation callbacks from its own threads. Correctness is guaranteed internally by two mechanisms, deliberately kept separate:- The node-local structure mutation is serialized on this instance's
monitor.
add(org.mockserver.mock.Expectation, org.mockserver.mock.listeners.MockServerMatcherNotifier.Cause),update(org.mockserver.mock.Expectation[], org.mockserver.mock.listeners.MockServerMatcherNotifier.Cause),reset(org.mockserver.mock.listeners.MockServerMatcherNotifier.Cause),removeHttpRequestMatcher(org.mockserver.matchers.HttpRequestMatcher, java.lang.String),trimEvictedFromBackend()andreconcileClusteredScan()mutate the non-thread-safehttpRequestMatchers(CircularPriorityQueue) andexpectationRequestDefinitions(CircularHashMap) only inside asynchronized(this)critical section. Each such section is short, purely in-memory, and — this is the load-bearing invariant — contains NO backend call, NO listener notification and NO blocking I/O. - The eviction reconcile protects in-flight adds. An add registers
its id in
addsInFlightbefore mutating the node-local cache and deregisters it only after its backend put returns; the trim excludes in-flight ids from eviction (seetrimEvictedFromBackend()).
add/updaterelease the monitor BEFOREexpectationBackend.put(...), andtrimEvictedFromBackend/reconcileClusteredScantake their backend snapshot BEFORE acquiring the monitor. This is the specific rule the first #2579 fix (reverted commit98ab5d8de) violated: it held this monitor acrossexpectationBackend.put, which for a clustered (Infinispan) backend is a blocking distributed round-trip, while the node's own invalidation listener needed the same monitor to progress — deadlocking the two nodes. Listener notifications (notifyListeners(org.mockserver.mock.RequestMatchers, org.mockserver.mock.listeners.MockServerMatcherNotifier.Cause)) are fired AFTER the monitor is released so a listener's blocking work (the file-persistence write lock and disk I/O) never runs under it. The READ / matching path (firstMatchingExpectation(org.mockserver.model.RequestDefinition),firstMatchingEarlyExpectation(org.mockserver.model.HttpRequest), theretrieve*family andsize()) is intentionally NOT synchronized — it relies on the CPQ's eventually-consistenttoSortedList()snapshot and must not be throttled by the mutator monitor. - The node-local structure mutation is serialized on this instance's
monitor.
-
getStateBackend
Returns the state backend, ornullif none has been set. -
applyConfigurationCapacity
public void applyConfigurationCapacity()Re-readmaxExpectationsfrom theConfigurationand resize the expectation store in place, so a change made viaPUT /mockserver/configurationactually takes effect instead of being accepted and ignored. A shrink evicts the eldest expectations immediately (firing the same eviction path as an overflow).When a state backend is wired (the default —
HttpStatealways wires one) the backend is the eviction authority and the node-local queue is deliberately unbounded (setStateBackend(org.mockserver.state.StateBackend)), so the resize is applied to the BACKEND and the node-local view is then reconciled to drop anything the backend evicted. Without a backend the node-local queue carries the bound and is resized directly. -
add
-
update
-
size
public int size() -
reset
-
reset
public void reset() -
firstMatchingExpectation
-
firstMatchingEarlyExpectation
-
clear
-
clearByNamespace
Clears only the expectations belonging to the given namespace (tenant), leaving expectations in other namespaces (and global expectations) intact. This lets a tenant clean up after itself on a shared MockServer instance.When
namespaceis blank this is a no-op (usereset()or a request-matcher clear for a full clear) so that a blank namespace filter never accidentally clears global expectations.- Parameters:
namespace- the namespace whose expectations to clearlogCorrelationId- correlation id for the resulting CLEARED log entry
-
clear
-
reconcileFromBackend
public void reconcileFromBackend()Reconciles the node-local HttpRequestMatcher cache against the backend KeyValueStore. This handles three cases:- Eviction: cached matchers whose id is no longer in the backend are removed (mirrors maxExpectations eviction).
- Remote add: backend entries with no local matcher get a new compiled HttpRequestMatcher (enables cross-node visibility under clustering).
- Remote update: backend entries whose version is newer than the locally cached version get their matcher rebuilt.
In single-node / no-backend mode this method is a no-op. When the backend is LOCAL (non-clustered), only eviction applies because all mutations originate locally and the CPQ is already in sync.
Threading contract (issue #2579): this method is deliberately NOT
synchronizedat the method level, because it makes backend calls (expectationBackend.size()/entries()) and no lock may span a backend call (the rule the reverted commit98ab5d8debroke, deadlocking a clustered backend). InsteadtrimEvictedFromBackend()andreconcileClusteredScan()take their backend snapshot OUTSIDE the monitor and then acquiresynchronized(this)for ONLY the short, in-memory mutation of the node-local CPQ. Concurrent remote invalidation events therefore serialise on that inner monitor (so the CPQ is never corrupted) without ever blocking on the network while holding it.Concurrent matching (data-plane) note: this method applies incremental per-entry mutations (add/update/remove) to the CPQ — the same granularity as normal control-plane add/remove. The CPQ's
toSortedList()provides an eventually-consistent sorted snapshot viaConcurrentSkipListSet + volatile sortedCache + filter(nonNull). A matching thread callingtoSortedList()during a reconcile may see a snapshot that lags by one mutation, but will never see a torn/empty view. This matches the pre-existing control-plane / data-plane concurrency contract. -
postProcess
-
retrieveRequestDefinitions
-
retrieveActiveExpectations
-
peekFirstMatchingExpectation
Side-effect-free probe: returns the first active expectation whose matcher matches the given request, WITHOUT consuming the match. Specifically, this method avoids:- Times decrement (
consumeMatch()) - Scenario state transition
responseInProgressflag- Metrics increment
Note on logging: the underlying
HttpRequestMatcher.matches()call may still emitINFO-levelEXPECTATION_MATCHEDorEXPECTATION_NOT_MATCHEDlog entries as a side-effect of the match evaluation. This method does not suppress those match-diagnostic logs. It is the Times/scenario/responseInProgress/metrics side-effects that are avoided.Used by the gRPC bidi router to decide the routing path before committing to a handler. Callers that need to actually consume the match (decrement Times, transition scenarios, emit logs) must still call
firstMatchingExpectation(RequestDefinition)separately on the committed path. - Times decrement (
-
retrieveExpectationsMatchingRequest
-
retrieveRequestMatchers
-
findClosestMatchDiff
-
findClosestMatchHint
Find the expectation that came closest to matching the request (fewest field differences) together with its id and field diff. Cold-path only — invoked when a request matched nothing and a diagnostic is being produced. Returnsnullwhen there are no expectations (or none produced a usable diff). -
isEmpty
public boolean isEmpty() -
getScenarioManager
-
notifyListeners
- Overrides:
notifyListenersin classMockServerMatcherNotifier
-