Repository navigation
Update dependency org.mock-server:mockserver-client-java to v9.2.0 - #3149
Merged
Merged
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
laurit
approved these changes
Oct 27, 2026
renovate
Bot
deleted the
renovate/org.mock-server-mockserver-client-java-9.x
branch
October 27, 2026 05:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
9.1.0→9.2.0Release Notes
mock-server/mockserver-monorepo (org.mock-server:mockserver-client-java)
v9.2.0Security
mockserver-netty-no-dependenciesjar and the Docker images, the bundled Kafka client used for AsyncAPI mocking now refuses theJndiLoginModuleandLdapLoginModuleJAAS login modules in asaslJaasConfig, as Kafka does by default since CVE-2023-25194. The jar renames the libraries it bundles, and the renaming changed the module names Kafka checks against, so the check never matched (#2869)dataPlaneAuthenticationRequiredon, a mocked endpoint could still be reached without credentials in several ways. A path that only starts with/mockserver(such as/mockserverfoo), or a/mockserver/...path that the server does not handle (such as/mockserver/nope, or/mockserver/statusand/readyover HTTP/3), skipped the check and was answered by any matching expectation, a catch-all one included. Over HTTP/1.1, an expectation withrespondBeforeBodyanswered the first request on a connection without checking credentials. And a gRPC bidirectional-streaming expectation (respondWithGrpcBidi) answered over HTTP/2 or HTTP/3 without checking them. gRPC server reflection also answered without credentials, listing the loaded services and message types, and so did a gRPC chaos fault and theUNIMPLEMENTEDanswer for an unknown gRPC method, which told the caller which methods exist. All of these are now refused (401, or gRPC statusUNAUTHENTICATED) like any other mocked endpoint. A refused request does not use up the expectation'sTimes, move its scenario on or count towards a gRPC chaos profile. The control plane, the readiness and liveness probes (the gRPC health check included), and CORS preflights for the control plane still work without data-plane credentials. Data-plane authentication does not apply to DNS mockingdataPlaneAuthenticationRequirednow applies to the WAR deployments (mockserver-war,mockserver-proxy-war) and toInProcessMockServer, which served their mocks without credentials whatever it said. Credentials are checked before a request can match an expectation, so a refused request uses up noTimesand moves no scenario, and a CORS preflight for a control-plane route stays open, as on the standalone server. An in-process request carries its credentials in its headers. In a WAR,tlsMutualAuthenticationRequiredis now enforced too: a mocked request over plain HTTP gets426 Upgrade Required, and one over HTTPS without a client certificate verified by the servlet container gets401; the container's trust store decides which certificates are accepted (see upgrading to 9).asyncKafkaSaslJaasConfigproperty (-Dmockserver.asyncKafkaSaslJaasConfigorMOCKSERVER_ASYNC_KAFKA_SASL_JAAS_CONFIG). A JAAS configuration names the Java login class the Kafka client loads, andPUT /mockserver/asyncapilet any control-plane caller choose it throughbrokerConfig.kafkaSecurity.saslJaasConfig. That request is now rejected with a400that names the startup property, and so is any other field inkafkaSecuritythat MockServer does not know, so no Kafka client setting that loads a class can be passed in;saslMechanismandsecurityProtocolmust be one of Kafka's own values. A request that selectsSASL_PLAINTEXTorSASL_SSLuses the startup JAAS configuration (or the JVM's own JAAS file). Because every SASL login is now held by the server, such a request may only use the startupasyncKafkaBootstrapServers: it must omitkafkaBootstrapServersor name the same brokers, or it gets a400and no credential is sent, and it gets a400while no startup bootstrap servers are set.asyncKafkaBootstrapServerscan now only be set at startup:PUT /mockserver/configurationignores it and logs a warning.PUT /mockserver/configurationignoresasyncKafkaSaslJaasConfigand logs a warning, andGET /mockserver/configurationnever returns it. To upgrade, move thesaslJaasConfigvalue from your requests toasyncKafkaSaslJaasConfig, setasyncKafkaBootstrapServersat startup to the brokers it logs in to, and keepsecurityProtocolandsaslMechanismin the request (see upgrading to 9)retrievewithforwardUnmatchedTonow needs a control-plane role that may change state. The parameter repoints where unmatched requests are forwarded, but control-plane authorization treated the call as a read, so withcontrolPlaneAuthorizationEnableda principal with only theREADrole could redirect unmatched traffic. Such a principal now gets403, as it already did forextractBodiesLargerThan; aretrievewithout either parameter is still a read (see upgrading to 9)undici7.30.1 (Dependabot alerts 610, 611, 612; the published package ships onlydist), and the VS Code extension's development dependency@vscode/vscenow resolves@azure/msal-node7.1 through an npmoverridesentry (alert 613; it reaches the build only through@vscode/vsce→@azure/identity, which has no release that allows msal-node 7 yet)protocexecutable it runs, can now only be set at startup:auditLogFile,persistedExpectationsPath,persistedRecordedExpectationsPath,persistedRecordedRequestsPath,memoryUsageCsvDirectoryanddirectoryToSaveDynamicSSLCertificate, likerecordedBodyFilesDirectory. Before,PUT /mockserver/configuration(or a client'supdateConfiguration) could change them on a running server, so anyone who could reach the control plane could redirect the audit log (before its first write) or the generated certificates and their private keys into another directory, such as one on the extension classpath, or point aformat=recordingdisk import (which readspersistedRecordedRequestsPath) at another file.grpcProtocPath, which names theprotocexecutable MockServer runs, is now startup-only for the same reason. APUTthat sets a different value is still answered, but the value is ignored and a warning is logged; aPUTthat sends the value already in force logs nothing. Set them with a system property, environment variable, properties file orConfigurationinstead (see upgrading to 9)controlPlaneTLSMutualAuthenticationRequired,controlPlaneTLSMutualAuthenticationCAChain, thecontrolPlaneJWTAuthentication*andcontrolPlaneOidc*settings,controlPlaneAuthorizationEnabled,controlPlaneScopeMapping,controlPlaneAuditEnabledanddashboardAnalyticsEndpoint, like thecontrolPlaneBasicAuthentication*settings. Before, anyone allowed to change the configuration withPUT /mockserver/configurationcould switch control-plane authentication off, trust their own keys or identity provider, give themselves a role, switch auditing off before making changes, or send every dashboard viewer's usage events to their own server. APUTthat sets a different value is still answered, but the value is ignored and a warning is logged, andGET /mockserver/configurationreports the value in force; the dashboard's Audit Trail view no longer has an on/off switch (see upgrading to 9)PUT /mockserver/configurationthat changes where the default LLM backend sends requests (llmBaseUrl, orllmProviderwhilellmBaseUrlis empty) without also supplyingllmApiKeynow clears the held key and logs a warning, so the key is never sent to the new endpoint. Before, anyone allowed to change the configuration could collect the key by pointingllmBaseUrlat their own server. APUTthat supplies both applies both; a JSONllmBackendsConfigvalue that changes a backend'sbaseUrlorproviderwhile leaving its key masked is not applied (see upgrading to 9)clusterVerifyFanInPeersandclusterFanInPeerAuthTokencan now only be set at startup:PUT /mockserver/configurationcould repoint the fan-in peers at any host, and every verify or retrieve then sent it the cluster peer token, which grants write access to every node. APUTthat supplies a different value is ignored and logs a warning. Thedetect_llm_driftMCP tool, which a read-only caller may use, now refuses a cassette whoseprovideris not the configured LLM backend's provider and sends nothing; before, it replayed the cassette with that provider's client, which, when nollmBaseUrlwas set, sent the heldllmApiKeyto that provider's default host. AnAZURE_OPENAIbackend with nollmBaseUrlnow sends nothing, instead of sending its key to the placeholder hostexample-resource.openai.azure.com. See upgrading to 9.noProxyHostscan now only be set at startup, like the upstream proxies it applies to (forwardHttpProxy,forwardHttpsProxy,forwardSocksProxy). Before, anyone allowed to change the configuration withPUT /mockserver/configurationcould add hosts to it and so route forwarded traffic around an upstream proxy relied on for logging or data-loss checks. APUTthat sets a different value is still answered, but the value is ignored and a warning is logged, andGET /mockserver/configurationreports the value in force (see upgrading to 9)Authorizationheader included, in the log, where anyone allowed to read logs (PUT /mockserver/retrieve?type=LOGS, including a read-only principal when control-plane authorization is on) and the dashboard could see it. Bearer tokens (JWT and OIDC) were exposed this way before this release. Every request the control-plane authentication gate sees is now logged with itsAuthorization,Proxy-Authorization, cookie and API-key headers masked, whateverredactSecretsInLogsays; authentication and authorization failure entries are masked the same way. Mocked (data-plane) requests are still logged exactly as they were sent, so verification by header is unchanged; the diagnostic entry for a cut-short or undecodable request masks credentials whatever its path. TRACE-level wire logging still prints raw traffic, headers included, to the console or log file (not to the event log), so keep TRACE logs private. If your control plane uses bearer tokens and others could read its logs, treat the tokens it logged as exposedAdded
"resetConnection": trueanswers a matched request with a TCP reset instead of a response, so the client sees "connection reset by peer" (ECONNRESET), wheredropConnectioncloses the connection cleanly (Java client:error().withResetConnection(true); also in the Kotlin DSL, every client library and the dashboard composer). Over TLS no TLS close message is sent first. Over HTTP/2 the whole connection is reset (usestreamErrorto reset one request); over HTTP/3, which has no TCP reset, the QUIC connection is closed withH3_INTERNAL_ERROR. Through a CONNECT or SOCKS tunnel the client's own connection is reset. WireMock and MountebankCONNECTION_RESET_BY_PEERfaults now import asresetConnection. Not available in the WAR deployment, like every error action. See error actionssystemValuehelper reads an environment variable or a system property, like WireMock'ssystemValue: Velocity$systemValue.env('APP_REGION')and$systemValue.property('app.version'), JavaScriptsystemValue.env('APP_REGION'), Mustache{{#systemValue.env}}APP_REGION{{/systemValue.env}}and{{#systemValue.property}}app.version{{/systemValue.property}}. It only reads names listed in the new startup-onlytemplateSystemValueAllowListproperty, like WireMock's--permitted-system-keys: a comma-separated list of exact names or prefixes ending in*, such asAPP_*,HOSTNAME. The list is empty by default, sosystemValuereturns an empty value for every name until you set it. A name that is not on the list also returns an empty value, the same as a name that is not set, and is logged as a warning once. A bare*is ignored, and there is no way to list names or values. One list covers both environment variables and system properties, and MockServer's own settings (names starting withmockserver.orMOCKSERVER_, in any case) and thejavax.net.sslkey and trust store passwords are never readable, whatever the list says; an entry that covers them is reported when MockServer starts, as is what templates can read.PUT /mockserver/configurationcannot change the list and logs a warning if asked to. Anyone who can create expectations, call them or read the log can read the values of the names you list, so list only values that are safe to share. Templates that do not usesystemValuerun as before. See templateSystemValueAllowListkotlin-wiremock: the neworg.mock-server:mockserver-client-kotlinartifact (also inmockserver-bom) adds Kotlin builders and extension functions toMockServerClientandClientAndServer, for examplemockServer.expect { request { method = "GET"; path = "/x" } respond { status = 200; body = "ok" } }. An expectation canrespond,forwardorerror, withtimes,timeToLive,priorityandid; requests match on method, path, headers, query and path parameters, cookies, an exact, JSON or any Java body matcher, andcustomize { }reaches every other Java builder method.verify(times) { }andverifySequence { }verify requests, andclear { },retrieveRecordedRequests { }andretrieveActiveExpectations { }take the same request block; standalonerequest { },response { }andexpectation { }builders return Java objects for any other client method. It brings inkotlin-stdlib2.4.20 and changes nothing for Java users. See Kotlin DSLDirectCallHttpServer:new InProcessMockServer()(inmockserver-core) handles requests as Java calls, for hosts such as AWS Lambda or a test of an HTTP adapter.handle(HttpRequest)returns theHttpResponse, andhandleAsync(HttpRequest)returns aCompletableFuture. Requests go through the same matching and actions as a running MockServer, so expectations, templates, delays, forwards (through MockServer's own HTTP client and any configuredforwardHttpProxy,forwardHttpsProxyorforwardSocksProxy), class callbacks, verification and the log behave the same. Most of the REST control plane works throughhandletoo, with control-plane authentication as configured; routes only the Netty server serves (/mockserver/configuration,/mockserver/ready,/mockserver/metrics, the dashboard) answer404.upsert,verify,retrieveRecordedRequests,resetandgetHttpState()give Java access to the control plane; they are in-JVM calls and are not authenticated, so ifhandlereceives untrusted traffic, enable control-plane authentication or drop/mockserver/paths first.handleblocks the caller for a response delay or a forward;handleAsyncdoes not. An unmatched request is answered404and never proxied to the host in itsHostheader. Object callbacks (which need a WebSocket; use a class callback), SSE, WebSocket and gRPC streaming, and connection faults are not available.close()stops every thread it started (a delayed response still pending completes when its delay elapses), and every method then throwsIllegalStateException. The Netty server is unchanged. See running in-process--print-all-network-traffic, for debugging only: start MockServer withlogRawNetworkTraffic=trueto log every read and write on each connection to MockServer, and on the HTTP requests it sends itself (forwards, proxied requests, webhooks), atINFOon theorg.mockserver.logging.RawNetworkTrafficLoglogger. HTTP/1.1 is shown as text after TLS decryption, HTTP/2 as decoded frames and other protocols as a hex dump, at most 4,096 bytes per read or write, with line breaks and control characters escaped so traffic cannot forge log lines.Authorization,Proxy-Authorization, cookies, API-key and other credential headers are masked, but bodies, query strings and hex dumps (including their text column) are logged as sent and may contain secrets, so do not use it in production. It can only be set at startup (PUT /mockserver/configurationignores it with a warning), and when it is off nothing is added to a connection. Not logged: HTTP/3, the upstream connections of proxied WebSockets and relayed binary connections, requests between cluster members, and metrics export.before,afterandequalToDateTime. Give a header, cookie, query-string or path-parameter value as an object such as{"after": "now -1 hours"}(Java client:withHeader(string("X-Issued-At"), NottableDateTimeString.after("now -1 hours"))) and the request value is compared as a date-time. The expected value isnow,nowwith an offset (now -1 hours,now +3 days,now PT30M) or an ISO-8601, RFC-1123 or epoch date-time. Optional fields set the request value's format (actualFormat: a pattern such asdd/MM/yyyy, oriso,rfc1123,epochSeconds,epochMillis), truncate either side (truncateExpected,truncateActual, such asfirst day of monthorstartOfDay), truncatenowafter its offset (applyTruncationLast), set the time zone used for values without one, truncation and calendar offsets (zone, default UTC) and negate the match (not).nowfollows the controllable clock. Parsing is strict, so 30 February never matches, and a request value that is not a date-time never matches, even when negated. An invalid matcher is rejected when the expectation is created, with the reason. Plain string values are unchanged, so a value that happens to readbefore:2026-01-01still matches that text, and expectations without date-time matchers do no extra work. Every client library models the object form and round-trips it: Gomockserver.After("now -1 hours")withHeaderMatcherand the other*Matcherbuilders; RustMatcherValue::after(..)withheader_matcherand the others; .NETMatcherValue.After(..); PythonDateTimeMatcher.after(..); RubyMockServer::DateTimeMatcher.after(..); PHPDateTimeMatcher::after(..)with the newheaderMatcher,queryStringParameterMatcher,cookieMatcherandpathParameterbuilders; and Node typings (DateTimeMatcher). Each haswith...options matching Java's, and plain string values are sent unchanged. The dashboard composer can edit a date-time value, keeps any object-form value it does not edit, and its code tabs generate these builders. The WireMock importer now mapsbefore,afterandequalToDateTimeheader and query predicates (withexpectedOffset,actualFormatand truncation) onto them. A failed expectation parse now also says why. See date-time matchingmatchesJsonPathandmatchesXPathwithbefore,afterorequalToDateTime. AJSON_PATHorXPATHbody matcher takes an optionaldateTimefield holding the same date-time matcher as a header value, for example{"type": "JSON_PATH", "jsonPath": "$.order.created", "dateTime": {"after": "now -1 days"}}(Java client:jsonPath("$.order.created", after("now -1 days"))orxpath("/order/@expires", after("now"))). The body then matches when a value the JSONPath selects, or the text of a node the XPath selects, is a matching date-time; when the expression selects several, one is enough. A JSON string is read as written and a whole number as epoch seconds or millis; an XPath must select nodes (elements or attributes). A body without the value, or whose value is not a date-time, does not match, even when the comparison is negated. JSONPath and XPath bodies withoutdateTimerun exactly as before, with no extra allocation. Every client library supports it: GoJSONPathBody(..).WithDateTime(mockserver.After(..)), RustBody::json_path(..).with_date_time(MatcherValue::after(..)), .NETBody.OfJsonPath(..).WithDateTime(MatcherValue.After(..)), PythonBody.json_path(.., DateTimeMatcher.after(..))andXPathBody(date_time=..), RubyBody.json_path(.., date_time: ..), PHPHttpRequest::jsonPathBody(.., DateTimeMatcher::after(..)), the newxpathBodyandmatcherBody, and Node typings (JsonPathBodyMatcher,XPathBodyMatcher). The dashboard composer has a Date-Time Comparison field for JSON Path and XPath bodies, and its code tabs generate these builders. The WireMock importer now mapsmatchesJsonPathandmatchesXPathwith abefore,afterorequalToDateTimeresult matcher onto them, instead of dropping the comparison with a warning. See date-time matching in a bodyhttpRequestTemplatein place ofhttpRequest(Java client:afterAction().withHttpRequestTemplate(template(...)); also modelled by the .NET (AfterAction.HttpRequestTemplate), Go (AfterAction.HttpRequestTemplate), Node (AfterAction.httpRequestTemplatetype), Python (AfterAction(http_request_template=...)), Ruby (AfterAction.new(http_request_template: ...)) and Rust (ExpectationAction::request_template) clients, kept verbatim by the PHP client, and offered as a Request template option for before/after actions in the dashboard composer and its code tabs). The template sees the triggering request with the same variables and helpers as response templates and must render a whole request; the rendered destination is checked byforwardProxyBlockPrivateNetworkswhen it is enabled (it is off by default), and a template that fails is logged and not sent (a blockingFAIL_FASTbefore-action returns 502). Escape request values in the template (Velocity$esc.java/$esc.url) and keep them out of the destination, or a caller can redirect the webhook. Webhooks usinghttpRequestand{$request...}runtime expressions are unchanged.dateshelper now works on any date and in any time zone, not only on the current time in UTC.dates.parsereads ISO-8601, RFC-1123, epoch seconds or millis, or text in a given pattern;dates.format(date, pattern, zone)formats any date;dates.offsetmoves a date by amounts such as"-2 hours"or an ISO-8601 period such asP1M;dates.truncate(and methods such asstartOfDay(),firstDayOfMonth(),lastDayOfMonth()andfirstDayOfNextYear()) moves it to the start of a period in its own time zone;dates.now(zone)gives the current time in a zone; anddates.rfc1123()writes the HTTP date format for headers such asExpiresandLast-Modified. The returned dates can be chained, for example$dates.parse($!request.headers['Date'][0]).plusHours(1).rfc1123(). Mustache, which cannot pass arguments, gets the no-argument forms ({{ dates.now.firstDayOfNextMonth.rfc1123 }}) and a{{#dates.render}}date|offset=1 days|format=yyyy-MM-dd{{/dates.render}}section. The current time still comes from the server clock, so clock control applies, and the existingdatesmethods return exactly what they did before. An invalid date, pattern, time zone, offset or truncation fails the template with an error naming the valueFILEresponse body can now pick its file from the request, like WireMock's templatedbodyFileName. Set"templatedFilePath": truewith atemplateTypeofMUSTACHEorVELOCITY, andfilePathis rendered against the request before the file is read, for example"filePath": "responses/{{ request.pathParameters.name.0 }}.json". The request-supplied part of the path must stay inside the directory named by the fixed part offilePathbefore the first template expression, and that fixed part must name a directory (not just/,./or a drive). A..or.segment, a leading or doubled/,\,:,%, a control character such as a null byte, or a symbolic link out of that directory is refused with the same500as a file that cannot be read. This is opt-in: withouttemplatedFilePath, or when the path has no template syntax, the path is used exactly as before. Every client library supports it: JavaFileBody.withTemplatedFilePath(true), Node typingstemplatedFilePath, PythonBody.file(..., templated_file_path=True), RubyBody.file(..., templated_file_path: true), GoFileBody.TemplatedFilePathandResponseBuilder.BodyFromTemplatedFile, .NETFileBody.TemplatedFilePathandWithTemplatedFileBody, RustBody::with_templated_file_path(true)and PHPfileBody(..., true); each omits the field when it is false, so existing expectations serialise unchanged. The dashboard composer has a matching option on a FILE body, and its generated code carries the flag in every languagerequestmodel in Velocity, Mustache and JavaScript now hasformData(the fields of anapplication/x-www-form-urlencodedbody),parts(the parts of amultipart/form-databody, each withname,filename,contentType,headers,bodyandbodyAsBase64),host,port,schemeandbaseUrl(from theHostheader or HTTP/2 and HTTP/3:authority, else the local address),bodyAsBase64, andid(the request's log correlation id). Each is worked out only when a template reads it, so other templates and non-templated responses cost nothing extra; a part'sbodyandbodyAsBase64are built only when read, so reading an upload'sfilenamedoes not copy its content. Form and multipart decoding is bounded (1,000 fields; 100 parts; 10 MiB, separate frommaxRequestBodySize), hitting a bound logs a warning that a template dry run reports, and a malformed body gives an empty map rather than an error.host,portandbaseUrlcome from the client-setHostheader;X-Forwarded-*headers are not used. In JavaScript these fields are not enumerated, soObject.keys(request)andJSON.stringify(request)are unchanged.PUT /mockserver/retrieve?type=RECORDED_EXPECTATIONS), matching WireMock's recording options.repeatsAsScenarios=trueturns a request that was recorded several times with different responses into one expectation that replays those responses in the recorded order and then keeps returning the last one; recordings count as the same request only when every header and other matched part is equal, and a request that got the same response every time is left as it was.captureHeaders=X-Tenant,Acceptkeeps only the named request headers in each recorded expectation (an empty value keeps none).extractBodiesLargerThan=<bytes>writes each larger response body to a file and returns aFILEbody that points at it; files are written only to the directory set by the newrecordedBodyFilesDirectoryproperty (empty by default, which disables this and makes such a retrieve a400; it can only be set at startup), are named after a SHA-256 of their contents, never after anything in the request, are never deleted by MockServer, and are capped at 1,000 files, 256 MB andmaxFutureTimeoutInMillisof writing per retrieve. With control-plane authorization on, a retrieve that writes files needs the MUTATE role. Extracted bodies are written exactly as recorded, secrets included (redactSecretsInRecordedExpectationsdoes not redact bodies), so use a private directory and review its files before sharing them. Each option is off unless given, and only changes the returned expectations, not the recorded traffic. The Java client takes them asretrieveRecordedExpectations(request, recordedExpectationsOptions().withRepeatsAsScenarios(true)...), which also carries the existingconsolidateandparameterizeoptions. See recording optionspickRandom,array,arrayJoin,range,formatXml,hostnameandtransformerParameters, and DataFaker locales. Thelistshelper haspickRandom,array,arrayJoinandrange(Velocity$lists.pickRandom('a', 'b'), JavaScriptlists.range(1, 5), Mustache{{#lists.pickRandom}}a|b{{/lists.pickRandom}}and{{#lists.range.1.5}}{{.}}{{/lists.range.1.5}});pickRandomfollowstemplateFakerSeed, and a list or range has at most 10,000 values.xpath.formatXml(xml)(Mustache{{#xpath.formatXml}}{{{ request.body }}}{{/xpath.formatXml}}) pretty-prints XML with the same DOCTYPE-refusing parser as the rest of MockServer, and fails on XML nested over 256 deep or output over four times its input (and over 64 KiB).hostnameis the server's host name: theHOSTNAMEenvironment variable when set (such as the pod name), otherwise looked up once in the background, never on the request path; it shows callers the server's internal name.faker.locale('de-DE')(Mustache{{ faker.locale.de-DE.name.fullName }}) gives DataFaker data for a locale; only the locales DataFaker ships are used (an unsupported country falls back to the language, an unsupported language to English with a warning), so a locale from request data such asAccept-Languagecannot grow memory.arrayJoinoutput is capped at 64 KiB or four times its values. A template can now carryparameters, a JSON object it reads asparameters.*($parameters.greeting,{{ parameters.greeting }},parameters.greeting), in response, forward, forward-response and webhook templates; it is in the JSON schema and the OpenAPI spec, and every client sets it: Javatemplate(...).withParameter("greeting", "hello")/withParameters(map), .NETHttpTemplate.OfType(...).WithParameter(...)/WithParameters(...), GoTemplateBuilder.Parameter(...)/Parameters(...)(HttpTemplate.Parameters), Node typingsparameters, PythonHttpTemplate(parameters=...)/with_parameter(...), RubyHttpTemplate.new(parameters: ...)/with_parameter(...), PHPHttpTemplate::withParameter(...)/withParameters(...), and RustExpectation::respond_template_with_parameters(...)/forward_template_with_parameters(...)andHttpOverrideForwardedRequest::response_template_with_parameters(...), read back withresponse_template_parameters()(the RustHttpTemplatestruct is unchanged, so code that builds it as a struct literal still compiles; the parameters are kept in the owning struct'sextramap, so a retrieved expectation keeps them when it is upserted again); each leaves the field out when unset. The dashboard composer has a Parameters field on response and forward templates, keeps a loaded template's parameters, and every code tab writes them. Templates that use none of these, and responses that are not templated, run as before: Velocity and Mustache hold the new helpers in their shared bindings, and JavaScript binds them only for a template that names them@MockServerInstancestarts several named MockServers for one test class, like WireMock's@ConfigureWireMock. Repeat it once per service; each instance gets its own free port and setsmockserver.<name>.portandmockserver.<name>.baseUrlin the SpringEnvironment, plus any property names you list inportPropertiesorbaseUrlProperties(these overrideapplication.propertiesand@TestPropertySource). AMockServerClientorClientAndServerfield annotated@InjectMockServer("<name>")receives that instance's client.initializationJsonPathloads expectations into one instance from a file, classpath resource, glob or file-system directory, andpropertiestakes per-instancemockserver.*settings. Each instance is reset after every test, then restores the expectations it had at startup, and stops when the last Spring context using it closes (usually when the JVM exits, or earlier with@DirtiesContext). Test classes that share an instance must not run in parallel in one JVM.@MockServerTestis unchanged and can be used alongside it. See several named MockServer instancesforwardProxyAllowedHostslists the only hosts that forward actions, proxied requests, replays and webhooks may go to, andforwardProxyDeniedHostslists hosts they must not go to; each is a comma-separated list of host names (api.example.com), domains (*.example.com, which also matchesexample.com), IP addresses and CIDR blocks (10.0.0.0/8). The deny list wins over the allow list. IP addresses and CIDR blocks are also matched against the address a name resolves to (an IPv4 address written as IPv6 is matched as IPv4, and a listed loopback address also covers::1,0.0.0.0and::), and where MockServer connects to the target itself it connects to the address it checked, so DNS rebinding cannot get round them; through an upstream proxy, which resolves the name itself, it can. A refused target is answered exactly as aforwardProxyBlockPrivateNetworksrefusal is (502for HTTP, a closed connection for binary and WebSocket proxying, an unsent webhook, and a warning). Both are fixed at startup:PUT /mockserver/configurationignores them with a warning, so a control-plane request cannot widen them. An invalid entry is rejected when set, and one that arrives another way (an environment variable or properties file) makes MockServer refuse every target until it is fixed. Both are empty by default, which leaves forwarding exactly as before. They also apply to fetching an OpenAPI spec from a URL, its remote$refs and redirects; specs read from a file or the classpath are unaffected. See forwardProxyAllowedHostswithClientIp:"clientIp": "10.0.0.0/8"matches requests whose client address is that IP address or falls in that CIDR range, IPv4 or IPv6, and"!10.0.0.0/8"matches every other address (JavawithClientIp, .NETWithClientIp/ClientIp, GoClientIP, Pythonwith_client_ip/client_ip, Rubywith_client_ip/client_ip:, Rustclient_ip(held inextra, so no public field changed), PHPclientIp, and the Node typings'clientIp). The client address is the connection's TCP peer, or the source a PROXY protocol header names when transparent proxying is on; it is never read fromX-Forwarded-For. With transparent proxying on, any client can send a PROXY header, soclientIpis a matching criterion, not access control. The range is parsed once when the expectation is created and an invalid value is rejected with the reason. Until the firstclientIpis applied in the JVM (the switch covers every MockServer instance in the process and is never cleared, even byreset), requests record their address as before, with nothing extra allocated per request; each connection's request mapper is 8 bytes larger (72 rather than 64). After that, each connection keeps one 24-byte holder that also retains the peer'sInetAddress(about 48 bytes for IPv4), and the address bytes (24 or 32 bytes) are derived on the first comparison. Matching adds no parsing, DNS lookup or allocation per request, and expectations without aclientIprun unchanged. It also filters verification and retrieved requests. The dashboard composer has a Client IP field, and its code generators emit each client's setter. Because the match diagnostics count every match field,totalFieldCountandmatchedFieldCountindebugMismatch,explainand the 404 closest-match diagnostic are one higher (19 rather than 18); the ranking of expectations is unchanged. An expectation rejected by a model validation now reports the reason instead of only "exception while parsing".metadataobject of any labels you want (string keys, any JSON values, such as an owner, a ticket number or tags), set when it is created and returned when it is retrieved. It never affects matching, and it is left out of the per-request match log.PUT /mockserver/retrieve?type=active_expectationsandPUT /mockserver/clearacceptmetadataKey(optionally withmetadataValue) andmetadataJsonPathto select expectations by their metadata; a text value is compared as text and any other value as JSON. In the Java client, usewithMetadata(...)when creating an expectation,retrieveActiveExpectationsByMetadata(...),clearByMetadata(...),clearByMetadataKey(...), and the newExpectationFilter. The Node, Python, Ruby, Go, .NET, Rust and PHP clients support metadata, retrieving and clearing by metadata or unmatched, and paging too, under their own naming (for exampleclear_by_metadatain Python and Ruby,ClearByMetadatain Go and .NET); each refuses a blank key, a null value or a clear that selects nothing before sending anything. The dashboard shows an expectation's metadata, keeps it when you edit the expectation, and includes it in the generated code. See creating expectationsunmatched=trueonPUT /mockserver/retrieve?type=active_expectationsreturns only the expectations that have never matched a request since they were created or last changed, and onPUT /mockserver/clearremoves them. Each MockServer instance keeps its own count. Java client:retrieveUnmatchedExpectations()andclearUnmatchedExpectations(). Like a namespace clear, a metadata or unmatched clear removes expectations only and leaves the event log as it is, and every filter you give (namespace, request matcher or id, metadata, unmatched) must match. An empty filter value (such asmetadataKey=),unmatched=falseon its own, a filter given twice, or a name that starts withmetadataorunmatchedbut is not a filter (such asmetadata_key) is rejected with400instead of clearing everything. Filter names are not case-sensitive; other unknown parameters are ignored, as elsewhere. Filter values are used literally, so a leading!or?is not a not or optional marker. A clear or retrieve whose query string cannot be decoded (for example a bare%) is now rejected with400rather than read as having no parameters, and so is a clear with an emptynamespace=. See clearing and resettingoffsetandlimitonPUT /mockserver/retrieveforactive_expectations(highest priority first, then oldest first) andrecorded_expectations(in the order received), applied after every other filter and for every format. Java client:retrieveActiveExpectations(request, ExpectationFilter)andretrieveRecordedExpectations(request, offset, limit). An invalid value, or one of these parameters on a type it does not apply to, is rejected with400. See retrieving active expectationsstaticFilesDirectoryproperty serves a directory of files forGETandHEADrequests that match no expectation and are not proxied, like WireMock's__filesdirectory. A request for/assets/site.cssgetsassets/site.cssfrom that directory, with aContent-Typefrom the file extension; a path ending in/gets that folder'sindex.html, and aHEADrequest gets the file'sContent-Lengthwithout the body. Expectations always win, and anything not served (a missing file, a folder, another method, a/mockserver/control-plane path, or a file over 50 MiB) still gets the usual404. Files are read on a small pool of their own, never on a network thread; with more than 1,000 reads waiting, further requests get a503(when MockServer runs as a WAR, files are read on the servlet container's request thread and this limit does not apply). Only plain paths inside the directory are served:.., encoded or double-encoded separators, backslashes, drive letters, control characters, hidden files and folders (names starting with.) and symbolic links out of the directory are refused, and folders are never listed. It is empty by default, which leaves unmatched requests exactly as before, and it is fixed at startup:PUT /mockserver/configurationignores it with a warning. See staticFilesDirectoryor: anhttpRequestholding ananyOflist matches a request when any one of its request matchers does, tried in order and stopping at the first match;"not": truebeside it matches when none does. Each entry can be the usual request fields, an OpenAPI matcher, anif/then/elseconditional or anotheranyOf, and a conditional's branches can now be ananyOf. It works for expectations, verification and retrieving recorded requests, and an emptyanyOfis rejected. Every client builds it: JavarequestAnyOf(...), .NETHttpRequest.RequestAnyOf/WithAnyOf, GoAnyOfRequest/RequestBuilder.AnyOf, PythonAnyOfRequestDefinition, RubyAnyOfRequestDefinition, RustHttpRequest::any_of(held inextra, so no public field changed), PHPHttpRequest::anyOf, and the Node typings'AnyOfRequestDefinition. When a request matches none of the entries,explain,explainUnmatchedanddebugMismatchgive the expectation's differences for the closest entry and add ananyOfobject naming it (closestBranch, counted from 0) with each entry's differing fields; the Python, Ruby, Go, .NET and PHP diagnostics models read it, and the dashboard's mismatch dialog shows it. The dashboard composer keeps ananyOf(and anif/then/else) request matcher exactly as loaded when an expectation is edited, rather than adding form fields beside it that the server would reject; its code tabs write the branches in every language, and its match preview evaluates each one. Expectations withoutanyOfare matched and indexed exactly as before; ananyOfexpectation is checked for every request (like a conditional one) and allocates nothing beyond its entries. See any of request matcherExtensionFactory: your own Java classes implementingorg.mockserver.extension.Extensionthat MockServer loads when it starts. Name them in the newextensionsproperty (--extensionson the command line,MOCKSERVER_EXTENSIONS,-Dmockserver.extensions,@MockServerTest/ per-instance@MockServerInstanceproperties) or pass classes or suppliers toConfiguration.extensions(...)when embedded;extensionScanning=truealso loads those listed inMETA-INF/servicesfiles. Each extension starts before MockServer builds anything else, is told the bound ports once they are bound (/mockserver/readywaits for it), and is stopped in reverse order when MockServer stops; an extension that also implementsExpectationInitializerseeds expectations at startup. A class that cannot be loaded, a duplicate or invalid name, or astartoronStartedthat throws or exceedsextensionStartTimeoutMillis(default 30 seconds) refuses start-up and stops the extensions already started. The three properties are fixed at startup:PUT /mockserver/configurationignores them with a warning, so the control plane can never load code. With no extension configured, nothing is scanned or loaded and request handling is unchanged. Works in the Netty server, both WARs (whereonStartedgets no ports) and embedded use. See extensionsAdminApiExtension: an extension that also implementsorg.mockserver.extension.AdminApiExtensionanswers every request under/mockserver/extensions/<name>/. The routes are part of the control plane: the same authentication, browser-origin check, CORS headers and audit log apply, and withcontrolPlaneAuthorizationEnabledevery route needs the role that may change state (GETincluded) unless the extension'sisReadOnlysays it only reads. The extension's code never runs on a network thread or MockServer's shared scheduler: at mostextensionAdminMaxInFlightcalls (default 32) run at once on a pool of their own, and a call beyond that, including one an extension makes back into MockServer, gets503(counted asextension_admininmock_server_overload_rejections); a call that has not answered withinextensionAdminTimeoutMillis(default 30 seconds) gets one500and is interrupted, and its late result is discarded. A call made while MockServer is stopping gets503and never reaches the extension. A thrown exception gets500with a correlation id. In a WAR the call runs on the request thread, with the limit but no timeout. Only loaded extension names are taken, so any other path under/mockserver/extensions/behaves as before; an expectation loaded at start-up for a path under a loaded name logs a warning, because it no longer matches. Both properties are fixed at startup. With no admin extension loaded, nothing is built and request handling is unchanged. See serving your own control-plane routesServeEventListener,StubLifecycleListenerandGlobalSettingsListener: an extension that implementsServeEventListeneris told about each request MockServer answered (mocked, forwarded or not matched, with the request, the response, the expectation id and when; bodies cut tomaxLoggedBodyBytes); one that implementsExpectationLifecycleListeneris told about each expectation added, updated or removed and why (API,FILE_INITIALISER,CLASS_INITIALISER,FILE_WATCHER,EXHAUSTED,EXPIRED,EVICTEDorCLUSTER; an expectation used up across a cluster isEXHAUSTEDand marked fleet-wide); and one that implementsSettingsListeneris told about eachPUT /mockserver/configuration. Each listener extension runs on its own thread, so a slow or stuck listener never slows MockServer: events wait in a queue bounded by the newextensionListenerQueueSize(default 10,000) and, for served requests and expectation changes, byextensionListenerMaxBytes(default about 5% of the heap, shared between those listeners, and weighed by the bodies and expectations the events hold), and anything that does not fit is dropped and counted inmock_server_extension_listener_dropped_totalwithout affecting the event log or verification. Both properties are fixed at startup. When MockServer stops, queued events are delivered for up tostopDrainMillisbefore the extensions stop. With no listener loaded, nothing changes: each removal or change of an expectation costs one check and allocates nothing. See listening to what MockServer doesTemplateHelperProviderExtension: an extension that implementsorg.mockserver.extension.TemplateHelperProvideradds its own helpers to response and forward templates underext.<extension name>—$ext.acme.Strings.upper($request.path)in Velocity,ext.acme.Strings.upper(request.path)in JavaScript (the methods of a public interface you choose), and{{#ext.acme.upper}}{{request.path}}{{/ext.acme.upper}}in Mustache, whose helper receives the section after it is rendered. Each call runs on a helper thread of its own, never on a network thread; all the calls one template makes share one budget, the newextensionHelperTimeoutMillis(default 1 second), after which a running call is interrupted and the template fails as any failing template does; at mostextensionHelperMaxInFlight(default 32) calls run at once, and one over the limit fails at once. OnlyhttpResponseTemplateandhttpForwardTemplatecan call helpers: a webhook, secondary action, delay, file body, stream, load scenario or dry-run template that calls one, or a mistyped helper name, fails with an error instead of rendering as written. Helpers work over HTTP/1.1, HTTP/2 and HTTP/3 alike. Both properties are fixed at startup. Without such an extension, templates are unchanged and do no extra work. See template helpersorg.mockserver.extension.RequestFiltersees every request that reaches expectation matching, over HTTP/1.1, HTTP/2 and HTTP/3 (plain and gRPC), in the WAR deployments and inInProcessMockServer(handleandhandleAsync), and can let it through unchanged (FilterResult.proceed()), change it (proceedWith(request)), or answer it (respond(response)). It never sees control-plane requests, a CORS preflight for a control-plane path, requests refused by data-plane authentication, requests answered over HTTP/1.1 by arespondBeforeBodyexpectation, gRPC bidirectional streams answered by agrpcBidiResponseexpectation, or the built-in gRPC health, reflection and chaos-profile answers, so a filter is not an access control: usedataPlaneAuthenticationRequiredfor that. It does see a browser's CORS preflight for a mocked endpoint (FilterContext.isCorsPreflight()), an unserved/mockserver/...path, and, over HTTP/3,/mockserver/statusand/mockserver/ready, which HTTP/3 answers by expectation matching. Filters run on their own threads (extensionDataPlaneMaxInFlight, default 32) with a time budget (extensionDataPlaneTimeoutMillis, default 1000 ms), never on the threads that serve connections; a gRPC call'sgrpc-timeoutincludes the time its filter takes. A filter that throws, times out, returnsnull, moves a request onto the control plane or changes a gRPC request's method gets a500naming the extension unless itsfailOpen()istrue(after a timeout, only if every later filter also fails open, and the later filters do not run); the event log records the received request once (as filtered), anINFOentry when a filter changed it, and aWARNfor each failure. Pipelined HTTP/1.1 responses stay in order. A filter may change where a proxied request goes;forwardProxyBlockPrivateNetworksand the forward host allow and deny lists apply to it only when they are turned on. A request whose filter is still running when MockServer stops is answered503at once over HTTP/3, when a WAR is undeployed and when anInProcessMockServeris closed; over HTTP/1.1 and HTTP/2 the graceful stop drain (stopDrainMillis) lets it finish first, and it gets the503only if it outlasts the drain. Either way the filter threads end. The website's Extensions page shows how to write and load one, andexamples/javahas a runnable filter. With no such extension loaded nothing changesInProcessMockServernow loads extensions configured withextensions(orconfiguration().extensions(...)); before, it ignored them. They start when the instance is created and stop in reverse order onclose()or a refused start;onStartedgets an empty list of ports. Admin routes are reached by passing/mockserver/extensions/<name>/...requests tohandle, with the same control-plane authentication as other REST requests, and run on the calling thread with noextensionAdminTimeoutMillis. Served-request and expectation listeners and$exttemplate helpers work as on a running server; a settings listener is never called, becausePUT /mockserver/configurationis not available in-process. With no extensions configured nothing changes./extensions, last on its classpath, sodocker run -v $(pwd)/ext:/extensions:ro -e MOCKSERVER_EXTENSIONS=com.example.AuditExtension mockserver/mockserveradds extensions without building an image, in every variant including-graaljs,-clusteredand-aot(use it instead of/libs, which those images fill themselves). Start-up is unchanged when nothing is mounted, and the images' start-up archives still apply. The Helm chart's newextensions.volumevalue mounts any volume (a ConfigMap, Secret, PersistentVolumeClaim, ...) read-only at/extensions, andextensions.classesandextensions.scanningset the two properties. MockServer now refuses to start when an enabled output file (persisted expectations, recorded expectations or recorded requests, the audit log or the memory-usage CSV files) would be written into the folder an extension was loaded from. See adding extensionsResponseTransformerV2: an extension that implementsorg.mockserver.extension.ResponseTransformersees each response MockServer sends over HTTP/1.1, HTTP/2 or HTTP/3, from the WAR deployments and fromInProcessMockServer(mocked, forwarded and proxied responses, and the responses MockServer makes itself, such as its404, overload503, breakpoint aborts, rate-limit429, chaos errors, failed templates and failed forwards, which it can tell apart withTransformContext.isGenerated()) and can return a changed one. It is given a copy, so a change it makes in place reaches nothing else. When it changes the body, aContent-Lengthit left unchanged is recalculated, but one it sets itself is sent as it is, so a wrong one breaks the response and its keep-alive connection; a forwarded body is as the upstream sent it, so a transformer that replaces a compressed body must also set or removeContent-Encoding. CORS anddefaultResponseHeadersare added after it, so it cannotConfiguration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.