The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
401 lines
24 KiB
Plaintext
401 lines
24 KiB
Plaintext
adapter-outbound-httpclient — 모듈 인벤토리
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-30T05:19:48+00:00
|
|
|
|
=== tracked file 총계 ===
|
|
$ git ls-files 'adapter/outbound/httpclient' | wc -l
|
|
370
|
|
exit=0
|
|
|
|
$ for d in $(git ls-files 'adapter/outbound/httpclient/src' | sed 's|^adapter/outbound/httpclient/src/||' | cut -d/ -f1 | sort -u); do n=$(git ls-files "adapter/outbound/httpclient/src/$d" | wc -l); l=$(git ls-files "adapter/outbound/httpclient/src/$d" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}'); echo "$d: $n files, $l LOC"; done
|
|
httpClientPerformanceTest: 7 files, 495 LOC
|
|
jmh: 2 files, 130 LOC
|
|
main: 260 files, 15004 LOC
|
|
test: 62 files, 6049 LOC
|
|
testkit: 35 files, 2754 LOC
|
|
exit=0
|
|
|
|
=== main 패키지 배치 ===
|
|
$ git ls-files 'adapter/outbound/httpclient/src/main' | grep '\.java$' | sed 's|.*/dev/caskeleton/||' | xargs -n1 dirname | sort | uniq -c | sort -rn
|
|
39 adapter/outbound/httpclient/resilience
|
|
29 adapter/outbound/httpclient/profile
|
|
23 adapter/outbound/httpclient/api/error
|
|
21 adapter/outbound/httpclient/security
|
|
19 adapter/outbound/httpclient/restclient
|
|
18 adapter/outbound/httpclient/auth
|
|
17 adapter/outbound/httpclient/webclient
|
|
14 adapter/outbound/httpclient/service
|
|
11 adapter/outbound/httpclient/dynamic
|
|
10 adapter/outbound/httpclient/transport
|
|
9 adapter/outbound/httpclient/api/result
|
|
7 adapter/outbound/httpclient/api/body
|
|
6 adapter/outbound/httpclient/reactor
|
|
6 adapter/outbound/httpclient/observation
|
|
6 adapter/outbound/httpclient/api/operation
|
|
5 adapter/outbound/httpclient/api
|
|
5 adapter/outbound/httpclient/apache
|
|
4 adapter/outbound/httpclient/migration
|
|
4 adapter/outbound/httpclient/jdk
|
|
4 adapter/outbound/httpclient/http3
|
|
3 adapter/outbound/httpclient/spring7
|
|
exit=0
|
|
|
|
=== test 패키지 배치 ===
|
|
$ git ls-files 'adapter/outbound/httpclient/src/test' | grep '\.java$' | sed 's|.*/dev/caskeleton/||' | xargs -n1 dirname | sort | uniq -c | sort -rn
|
|
8 adapter/outbound/httpclient/resilience
|
|
7 adapter/outbound/httpclient/contract
|
|
5 adapter/outbound/httpclient/webclient
|
|
5 adapter/outbound/httpclient/service
|
|
5 adapter/outbound/httpclient/security
|
|
5 adapter/outbound/httpclient/restclient
|
|
3 adapter/outbound/httpclient/architecture
|
|
2 adapter/outbound/httpclient/testkit
|
|
2 adapter/outbound/httpclient/profile
|
|
2 adapter/outbound/httpclient/observation
|
|
2 adapter/outbound/httpclient/migration
|
|
2 adapter/outbound/httpclient/jdk
|
|
2 adapter/outbound/httpclient/dynamic
|
|
2 adapter/outbound/httpclient/auth
|
|
2 adapter/outbound/httpclient/api/body
|
|
2 adapter/outbound/httpclient/apache
|
|
1 adapter/outbound/httpclient/transport
|
|
1 adapter/outbound/httpclient/spring7
|
|
1 adapter/outbound/httpclient/http3
|
|
1 adapter/outbound/httpclient/api/operation
|
|
1 adapter/outbound/httpclient/api/error
|
|
1 adapter/outbound/httpclient/api
|
|
exit=0
|
|
|
|
=== main 상위 30 ===
|
|
$ git ls-files 'adapter/outbound/httpclient/src/main' | grep '\.java$' | xargs wc -l | sort -rn | head -31
|
|
15004 total
|
|
330 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/DefaultReactiveHttpGateway.java
|
|
304 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/DefaultGenericHttpGateway.java
|
|
291 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/profile/ClientProfileValidator.java
|
|
272 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/service/ReactiveKernelHttpExchangeAdapter.java
|
|
269 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingAttemptExecutor.java
|
|
228 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DefaultDynamicTargetGateway.java
|
|
215 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/IpAddressClassifier.java
|
|
211 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/service/KernelHttpExchangeAdapter.java
|
|
206 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/RestClientResponseReader.java
|
|
190 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/http3/JettyHttp3TransportProvider.java
|
|
187 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/profile/ClientRuntimeRegistry.java
|
|
184 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveAttemptExecutor.java
|
|
183 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/DefaultReactiveSseGateway.java
|
|
176 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedTargetPolicy.java
|
|
176 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsMaterialProvider.java
|
|
169 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingStreamingGateway.java
|
|
165 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/api/error/HttpFailureMetadata.java
|
|
154 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/service/ServiceOperationDescriptorScanner.java
|
|
153 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/apache/ApacheClientFactory.java
|
|
152 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/api/operation/HttpOperation.java
|
|
148 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/SingleFlightTokenLoader.java
|
|
144 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/RestClientRuntimeFactory.java
|
|
140 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/apache/ApacheFailureClassifier.java
|
|
137 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/reactor/ReactorHttpClientFactory.java
|
|
137 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/reactor/ReactorFailureClassifier.java
|
|
135 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/resilience/AttemptResiliencePipeline.java
|
|
133 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/resilience/DefaultRetryEligibilityEngine.java
|
|
132 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingClientRuntime.java
|
|
128 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/OAuth2CredentialProvider.java
|
|
123 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/WebClientRuntimeFactory.java
|
|
exit=0
|
|
|
|
=== 비 Java 파일 ===
|
|
$ git ls-files 'adapter/outbound/httpclient' | grep -v '\.java$'
|
|
adapter/outbound/httpclient/CLAUDE.md
|
|
adapter/outbound/httpclient/README.md
|
|
adapter/outbound/httpclient/build.gradle
|
|
adapter/outbound/httpclient/gradle.lockfile
|
|
exit=0
|
|
|
|
=== 레지스트리 엔트리 ===
|
|
$ python3 -c "
|
|
import json;d=json.load(open('config/architecture/modules.json'))
|
|
for m in d['modules']:
|
|
if m['id']=='adapter-outbound-httpclient': print(json.dumps(m,ensure_ascii=False,indent=2))
|
|
"
|
|
{
|
|
"id": "adapter-outbound-httpclient",
|
|
"gradle_path": ":adapter:outbound:httpclient",
|
|
"source_path": "src/adapter/outbound/httpclient",
|
|
"allowed_dependencies": [
|
|
"domain-core",
|
|
"application-core",
|
|
"shared-contract",
|
|
"adapter-outbound-support"
|
|
],
|
|
"runtime_memberships": [
|
|
"app-bootstrap"
|
|
]
|
|
}
|
|
exit=0
|
|
|
|
=== build.gradle ===
|
|
$ cat -n 'adapter/outbound/httpclient/build.gradle'
|
|
1 // Outbound HTTP Client Platform leaf — see
|
|
2 // docs/superpowers/specs/2026-08-08-httpclient-platform-design.md (design package) and
|
|
3 // docs/httpclient/repository-adaptation.md (how the design's 19 library modules map here).
|
|
4 //
|
|
5 // The design models the platform as 19 separate Gradle modules. This repository's fail-closed
|
|
6 // module registry (src/config/architecture/modules.json) outranks that layout, so the module
|
|
7 // boundaries are packages under dev.caskeleton.adapter.outbound.httpclient and
|
|
8 // HttpClientModuleBoundaryTest enforces the design's module dependency table.
|
|
9 description = 'Outbound adapter: HTTP client platform (typed clients, profiles, evidence-based retry)'
|
|
10
|
|
11 dependencies {
|
|
12 implementation project(':application-core')
|
|
13 implementation project(':shared-contract')
|
|
14 implementation project(':adapter:outbound:support')
|
|
15
|
|
16 // Spring client layer. `httpclient-core-api` must not reach these; ArchUnit enforces it.
|
|
17 implementation 'org.springframework.boot:spring-boot-autoconfigure'
|
|
18 implementation 'org.springframework:spring-web'
|
|
19 implementation 'org.springframework:spring-webflux'
|
|
20 implementation 'io.projectreactor:reactor-core'
|
|
21
|
|
22 // Transport providers. Apache HC5 is the blocking default, JDK HttpClient is the lightweight
|
|
23 // alternative (JDK built-in), Reactor Netty is the reactive default, Jetty carries the
|
|
24 // Experimental HTTP/3 transport that the Stable starter never auto-configures.
|
|
25 implementation 'org.apache.httpcomponents.client5:httpclient5'
|
|
26 implementation 'io.projectreactor.netty:reactor-netty-http'
|
|
27 implementation 'org.eclipse.jetty:jetty-client'
|
|
28 // HTTP/3 is Experimental and off by default, so its transport is compileOnly plus a test
|
|
29 // dependency rather than a runtime one. It used to be `implementation`, which put the whole
|
|
30 // QUIC/HTTP-3/QPACK stack on every deployment's runtime classpath — megabytes and an attack
|
|
31 // surface — to serve a feature the Stable starter never auto-configures. A deployment that
|
|
32 // opts into HTTP/3 adds `org.eclipse.jetty.http3:jetty-http3-client-transport` itself, and
|
|
33 // Http3CapabilityReport already refuses the transport when those classes are absent, so the
|
|
34 // failure mode is a startup error rather than a NoClassDefFoundError mid-call.
|
|
35 compileOnly 'org.eclipse.jetty.http3:jetty-http3-client-transport'
|
|
36 testImplementation 'org.eclipse.jetty.http3:jetty-http3-client-transport'
|
|
37
|
|
38 // Resilience4j supplies the execution primitives only. HTTP retry *eligibility* is owned by
|
|
39 // this module (design D-09) and never delegated to a generic retry library.
|
|
40 implementation 'io.github.resilience4j:resilience4j-retry:2.2.0'
|
|
41 implementation 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
|
|
42 implementation 'io.github.resilience4j:resilience4j-ratelimiter:2.2.0'
|
|
43 implementation 'io.github.resilience4j:resilience4j-bulkhead:2.2.0'
|
|
44 implementation 'io.github.resilience4j:resilience4j-micrometer:2.2.0'
|
|
45
|
|
46 implementation 'org.springframework.security:spring-security-oauth2-client'
|
|
47 implementation 'com.fasterxml.jackson.core:jackson-databind'
|
|
48 // Jackson 3, and not optional. `RestClientResponseReader.defaultConverters()` constructs a
|
|
49 // `JacksonJsonHttpMessageConverter`, which is Spring 7's converter and is built on
|
|
50 // `tools.jackson.databind.json.JsonMapper`. javac never noticed: the constructor's signature
|
|
51 // does not mention the type, so compiling the call needs nothing. Loading the class does, and
|
|
52 // Jackson 3 reached only this leaf's test and jmh classpaths — so the reader worked in every
|
|
53 // test and would have thrown NoClassDefFoundError in any deployment that did not happen to
|
|
54 // have Jackson 3 from somewhere else. The Eclipse compiler the editor runs reported it as an
|
|
55 // error, which is what it is. Jackson 2 stays: two files still read `JsonNode` with it.
|
|
56 implementation 'tools.jackson.core:jackson-databind'
|
|
57 implementation 'io.micrometer:micrometer-core'
|
|
58 implementation 'org.slf4j:slf4j-api'
|
|
59
|
|
60
|
|
61 // Testkit dependencies (design §28.1 test topology). They are test-scoped so no production
|
|
62 // module can depend on the testkit; the testkit source set inherits them by extending
|
|
63 // testImplementation, and the test lanes use them directly.
|
|
64 testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
|
|
65 testImplementation 'com.squareup.okhttp3:okhttp-tls:4.12.0'
|
|
66 testImplementation 'org.testcontainers:testcontainers'
|
|
67 testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
|
|
68 testImplementation 'org.testcontainers:testcontainers-toxiproxy'
|
|
69 testImplementation 'io.projectreactor:reactor-test'
|
|
70 testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
|
71 testImplementation 'io.projectreactor.tools:blockhound:1.0.17.RELEASE'
|
|
72 }
|
|
73
|
|
74 // The testkit is its own source set, not part of `test`, because three lanes consume it and only
|
|
75 // one of them is a test lane. Reaching into `sourceSets.test.output` from `jmh` compiled under
|
|
76 // Gradle and could not be modelled by the IDE at all: a source set is test source there only when
|
|
77 // a Test task runs its output, `jmh` is driven by JavaExec, and IDE main source may not read IDE
|
|
78 // test source — so every testkit reference in the benchmarks was an unresolved type in the editor
|
|
79 // while the build was green. A source set nobody runs tests from is main source for all three
|
|
80 // consumers, which is what it always was.
|
|
81 //
|
|
82 // Performance certification and JMH benchmarks are separate source sets for their own reason: they
|
|
83 // are slow, they assert on resource bounds rather than behaviour, and they must never be part of
|
|
84 // the default unit lane.
|
|
85 strictTestLanes {
|
|
86 // The testkit compiles against exactly what a test does: `implementation` inheritance runs
|
|
87 // through testImplementation, so this is the module's own dependencies plus the test libraries.
|
|
88 sourceSet('testkit') { compilesAgainst 'main' }
|
|
89 sourceSet('httpClientPerformanceTest') { compilesAgainst 'main', 'testkit' }
|
|
90 sourceSet('jmh') { compilesAgainst 'main', 'testkit' }
|
|
91 }
|
|
92
|
|
93 // Every test lane compiles and runs against the testkit.
|
|
94 sourceSets.test {
|
|
95 compileClasspath += sourceSets.testkit.output
|
|
96 runtimeClasspath += sourceSets.testkit.output
|
|
97 }
|
|
98
|
|
99 dependencies {
|
|
100 // Testkit dependencies (design §28.1 test topology). They stay off the production
|
|
101 // configurations, so no production module can depend on the testkit.
|
|
102 testkitImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
|
|
103 testkitImplementation 'com.squareup.okhttp3:okhttp-tls:4.12.0'
|
|
104 testkitImplementation 'org.testcontainers:testcontainers'
|
|
105 testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter'
|
|
106 testkitImplementation 'org.testcontainers:testcontainers-toxiproxy'
|
|
107 testkitImplementation 'io.projectreactor:reactor-test'
|
|
108 testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
|
109 testkitImplementation 'io.projectreactor.tools:blockhound:1.0.17.RELEASE'
|
|
110
|
|
111 jmhImplementation 'org.openjdk.jmh:jmh-core:1.37'
|
|
112 jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
|
|
113 }
|
|
114
|
|
115 // UTF-8 is pinned for every JavaCompile task in the root build; this leaf no longer repeats it.
|
|
116
|
|
117 // JMH generates its harness classes at compile time. They are not our source, so the
|
|
118 // compile-time checker and -Werror are switched off for that source set only; applying them
|
|
119 // would fail the build on generated code we cannot edit.
|
|
120 tasks.named('compileJmhJava', JavaCompile) {
|
|
121 options.errorprone.enabled = false
|
|
122 options.compilerArgs.removeIf { it == '-Werror' }
|
|
123 }
|
|
124
|
|
125 // The bytecode analyser is disabled for the same generated harness, for the same reason.
|
|
126 tasks.named('spotbugsJmh') {
|
|
127 enabled = false
|
|
128 }
|
|
129
|
|
130 // Takes a Test task. The parameter is left untyped because the IDE's Gradle parser has no Gradle
|
|
131 // API on its classpath and reports the annotation as an unresolved type; Groovy dispatches the
|
|
132 // calls below dynamically either way.
|
|
133 Closure<Void> applyContractSelection = { task ->
|
|
134 // Cross-transport contract lane. The same semantic contract runs against every Stable transport;
|
|
135 // the transport under test is selected explicitly so a missing transport is an error, not a skip.
|
|
136 task.systemProperty 'httpclient.contract.transports',
|
|
137 (project.findProperty('httpclient.contract.transports') ?: 'apache,jdk,reactor').toString()
|
|
138 // Netty's strictest leak detector is on for every lane. It is only meaningful if it is actually
|
|
139 // live, so NettyLeakDetectionExtension asserts the level rather than trusting the flag reached
|
|
140 // the forked JVM.
|
|
141 task.systemProperty 'io.netty.leakDetection.level', 'paranoid'
|
|
142 // HTTP/3 is Experimental: it is never part of the default lane and never silently skipped.
|
|
143 task.systemProperty 'httpclient.http3.tests.enabled',
|
|
144 (project.findProperty('http3.tests.enabled') ?: 'false').toString()
|
|
145 }
|
|
146
|
|
147 tasks.named('test', Test) {
|
|
148 applyContractSelection(it)
|
|
149 // Two lanes are excluded from the default run for opposite reasons: the fault lane needs Docker
|
|
150 // and fails closed without it, and the BlockHound lane rewrites core JDK bytecode, which must
|
|
151 // not be imposed on every unit run.
|
|
152 useJUnitPlatform {
|
|
153 excludeTags 'quarantine', 'httpclient-fault', 'httpclient-blockhound'
|
|
154 }
|
|
155 }
|
|
156
|
|
157 // Four tag-selected lanes, declared rather than assembled.
|
|
158 //
|
|
159 // Two of them — the stable contract suite and the security suite — did not carry
|
|
160 // failOnNoDiscoveredTests at all. Five lanes were written by copying the block above, and the
|
|
161 // property that makes a lane mean anything was lost on two of the copies, so the cross-transport
|
|
162 // contract suite and the SSRF/credential-leak suite would each have reported success on discovering
|
|
163 // nothing. Declaring the lanes removes the opportunity: the convention has no opt-out.
|
|
164 strictTestLanes {
|
|
165 lane('httpClientBlockHoundTest') {
|
|
166 tag = 'httpclient-blockhound'
|
|
167 description = 'Proves no platform code blocks a Reactor event loop (design §18.2, §28.6).'
|
|
168 customize = { test ->
|
|
169 applyContractSelection(test)
|
|
170 // BlockHound instruments already-loaded JDK classes; Java 13+ needs this to redefine them.
|
|
171 test.jvmArgs '-XX:+AllowRedefinitionToAddDeleteMethods'
|
|
172 }
|
|
173 }
|
|
174 lane('httpClientStableContractTest') {
|
|
175 tag = 'httpclient-contract'
|
|
176 description = 'Runs the cross-transport stable contract suite (design §28.2, §33).'
|
|
177 customize = { test -> applyContractSelection(test) }
|
|
178 }
|
|
179 lane('httpClientSecurityTest') {
|
|
180 tag = 'httpclient-security'
|
|
181 description = 'Runs the SSRF, credential-leak, and cardinality suite (design §28.5, §28.7).'
|
|
182 customize = { test -> applyContractSelection(test) }
|
|
183 }
|
|
184 // Its own source set rather than a tag, so the source set is the selection.
|
|
185 lane('httpClientPerformanceTest') {
|
|
186 sourceSet = 'httpClientPerformanceTest'
|
|
187 description = 'Certifies pool, streaming, retry, and rotation resource bounds (design §28.8).'
|
|
188 customize = { test ->
|
|
189 applyContractSelection(test)
|
|
190 test.systemProperty 'performance.assertions.enabled',
|
|
191 (project.findProperty('performance.assertions.enabled') ?: 'false').toString()
|
|
192 }
|
|
193 }
|
|
194 lane('httpClientFailureInjectionTest') {
|
|
195 tag = 'httpclient-fault'
|
|
196 description = 'Runs the Toxiproxy fault-injection suite; fails closed without Docker ' +
|
|
197 '(design §28.3).'
|
|
198 customize = { test ->
|
|
199 applyContractSelection(test)
|
|
200 // The upstream image is mutable by default. Passing a digest here is what makes a red
|
|
201 // fault run attributable to this repository rather than to someone else's image push.
|
|
202 test.systemProperty 'httpclient.fault.httpbin.image',
|
|
203 (project.findProperty('httpclient.fault.httpbin.image')
|
|
204 ?: 'kennethreitz/httpbin:latest').toString()
|
|
205 }
|
|
206 }
|
|
207 }
|
|
208
|
|
209
|
|
210 tasks.register('jmh', JavaExec) {
|
|
211 group = 'verification'
|
|
212 description = 'Runs the JMH benchmarks for the blocking and reactive clients (design §28.8).'
|
|
213 mainClass = 'org.openjdk.jmh.Main'
|
|
214 classpath = sourceSets.jmh.runtimeClasspath
|
|
215 args '-rf', 'json', '-rff', layout.buildDirectory.file('reports/jmh/result.json').get().asFile.absolutePath
|
|
216 }
|
|
217
|
|
218 // Spring 6.2 / 7.0 compatibility lanes. This repository's Spring Boot 4.0 baseline pins Spring
|
|
219 // Framework 7, so the 6.2 lane verifies the *API surface* the common packages compile against
|
|
220 // rather than executing on a 6.2 distribution; the limitation is recorded in
|
|
221 // docs/httpclient/support-matrix.md instead of being hidden behind a green check.
|
|
222 tasks.register('spring62ApiSurfaceScan', Test) {
|
|
223 group = 'verification'
|
|
224 description = 'Scans the common packages for Spring 6.2 API-surface confinement. NOT a 6.2 runtime.'
|
|
225 testClassesDirs = sourceSets.test.output.classesDirs
|
|
226 classpath = sourceSets.test.runtimeClasspath
|
|
227 useJUnitPlatform { includeTags 'httpclient-spring62-surface' }
|
|
228 failOnNoDiscoveredTests = true
|
|
229 outputs.upToDateWhen { false }
|
|
230 }
|
|
231
|
|
232 // Which lanes gate an ordinary build, and which do not.
|
|
233 //
|
|
234 // The specialised lanes existed but hung off nothing: `check` ran only `test`, so the SSRF suite,
|
|
235 // the BlockHound lane, the cross-transport contract and the Spring 6.2 surface scan were green in
|
|
236 // CI only because a workflow happened to name them, and green locally because nobody ran them.
|
|
237 // The four below are hermetic and fast — no Docker, no network, no machine-dependent thresholds —
|
|
238 // so they belong in `check`.
|
|
239 //
|
|
240 // httpClientFailureInjectionTest (needs Docker), httpClientPerformanceTest (machine-dependent
|
|
241 // bounds) and jmh (minutes) stay out deliberately. Attaching them would make `check` fail on a
|
|
242 // laptop without Docker, which teaches people to skip `check`.
|
|
243 tasks.named('check') {
|
|
244 dependsOn 'httpClientStableContractTest',
|
|
245 'httpClientSecurityTest',
|
|
246 'httpClientBlockHoundTest',
|
|
247 'spring62ApiSurfaceScan'
|
|
248 }
|
|
249
|
|
250 tasks.register('spring70CompatibilityTest', Test) {
|
|
251 group = 'verification'
|
|
252 description = 'Runs the contract suite on the repository Spring 7 baseline (design §29).'
|
|
253 testClassesDirs = sourceSets.test.output.classesDirs
|
|
254 classpath = sourceSets.test.runtimeClasspath
|
|
255 useJUnitPlatform { includeTags 'httpclient-contract' }
|
|
256 applyContractSelection(it)
|
|
257 outputs.upToDateWhen { false }
|
|
258 }
|
|
exit=0
|
|
|
|
=== leaf 밖 소비자 ===
|
|
$ git grep -l 'dev.caskeleton.adapter.outbound.httpclient' -- . | grep -v '^adapter/outbound/httpclient/' | sed 's|/src/.*||' | sort | uniq -c
|
|
17 app-bootstrap
|
|
exit=0
|
|
|