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>
164 lines
14 KiB
Plaintext
164 lines
14 KiB
Plaintext
Sub-scope 04 (httpclient) restclient/** + webclient/** — manifest and probes
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-30T05:31:16+00:00
|
|
|
|
=== OWNED FILES ===
|
|
3317df91c6c64d62d0c1b0ac6634f8d2209a5503 main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingAttemptExecutor.java 269
|
|
5e5cb1380bdb79df615d583ab3e644fb000e9c57 main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingClientRuntime.java 132
|
|
06d394b8d3116511a7407980e57276e067cb87db main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingExecutionSupport.java 86
|
|
d4e4d3188f8fb13ee72a849e101303bfbde4a83e main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingOperationContext.java 41
|
|
8b0b348f9ff5f5577a913f934d2ced7f163cd6a5 main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingRedirectCoordinator.java 114
|
|
f0c6a30368119d4fb939f8850b23603f5d945759 main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingResponseMapper.java 78
|
|
9589a9f172c77738aaab1a689e812d3185ccc5bf main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingStreamingGateway.java 169
|
|
90e67085b700cfdfe637b2c8643a8b2054035a6e main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BoundedErrorBody.java 54
|
|
bad7193da46442324dcc5bc325870c2b3ff3f156 main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/CountingBoundedInputStream.java 50
|
|
6c72a175d065e5ae317c6992120a166661bae83b main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/DefaultBlockingStreamingResponse.java 67
|
|
17e787852f7b4762ff280b68bc510a5f986be3ee main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/DefaultGenericHttpGateway.java 304
|
|
473cf6085dfdd827822e938915f5bf43321f36ef main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/GenericHttpGateway.java 26
|
|
35f9f94dff3fdcc2f24cf29f99cfbca04466e352 main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/RemoteProblemDecoder.java 88
|
|
e88961bc440727c875270af1e8b6e559db0a53ec main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/ResponseSizeLimiter.java 62
|
|
ce9cc87429faccd4effe19ada9b017187485abd8 main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/RestClientBodyWriter.java 76
|
|
de93a1bec3b600f203894ff5d9641e041a9db54c main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/RestClientResponseReader.java 206
|
|
42518b6727c4dfc884f9bec5eb29752b8272b132 main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/RestClientRuntimeFactory.java 144
|
|
e4305dce6f69236a4465bafa9328e53a5f1cf3ce main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/StableBlockingExceptionMapper.java 40
|
|
fdd77212d4830a37192a6550834673d31d45579b main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/StatusHandlingPolicy.java 12
|
|
f7e354c4783a8d1cfa91589c7c08ad51eb3e245f main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/BoundedDataBufferFlux.java 39
|
|
158094336c522fda6ee08642b47b512cdd32536f main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/DefaultReactiveHttpGateway.java 330
|
|
5b9588c32fd9099c168b058f7a23e40878b1838c main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/DefaultReactiveSseGateway.java 183
|
|
33ff26665dca2064611531748084a305826b13fe main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/FirstByteDeliveryGuard.java 24
|
|
f4ca87199cfb463223d496f950d2b2e87a952ed2 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/MultipartReplayability.java 37
|
|
36f2d940765828913d1adbf5eee49ecf82d1a927 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveAttemptExecutor.java 184
|
|
b193badebdecca660699403d87b7847bb2e802d5 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveBodySource.java 38
|
|
ad4f8e67414ef821f8d49822bfb4b7dca138b1c2 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveClientRuntime.java 111
|
|
dbadac6d34f55b4b5795988e83448e1cf3732d51 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveHttpGateway.java 18
|
|
f250530402a4b0c849e8d7cbbc117faca481c030 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveSseGateway.java 13
|
|
134d3c2baa033be52419c164b4792f3501c92a30 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveStreamingGateway.java 107
|
|
e5f5779004a000c5bb830f7ed512dcf5f77ca342 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/SseIdleTimeoutException.java 22
|
|
f82ef76d8b89077ee50820179ab93d287ef9cae4 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/SseOperation.java 49
|
|
90f65c72d6580e715f7bd72e49d6d236b81ac8f8 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/SseReconnectPolicy.java 28
|
|
ece8e712de4c2dd97ba98a9a182e972a7d8dc24f main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/WebClientBodyWriter.java 98
|
|
225bd9843d62843034dcab53ac51fa2497f5ea45 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/WebClientResponseMapper.java 54
|
|
9c2e47f592ae3462e72e7b4a803f1b37bba3df71 main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/WebClientRuntimeFactory.java 123
|
|
b1990e1b941ed8157e1ae3964c7c5e3d4323511f test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingRedirectCoordinatorTest.java 175
|
|
8acd49e49ff84f29f2c539b875d7b78c3c97e65b test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingResponseMapperTest.java 63
|
|
0f841cd744743ac058c99ca2c841593d4ecc01fa test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingStreamingLifecycleTest.java 145
|
|
a88e2e1c08e8e037a4124d30db718bdc82c0de57 test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/DefaultGenericHttpGatewayTest.java 200
|
|
b308025e96311ce3b8d80359ff3431f9b28bff87 test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/ResponseSizeLimiterTest.java 37
|
|
d4241ae02359c7306800a410da417a746593ad8e test/java/dev/caskeleton/adapter/outbound/httpclient/webclient/BlockHoundEventLoopTest.java 110
|
|
fa0419d599ca04b74b29762ceefe0724de505074 test/java/dev/caskeleton/adapter/outbound/httpclient/webclient/DefaultReactiveHttpGatewayTest.java 95
|
|
fb8fe20deeee336dd793926f2ab3dd8cf7b343ff test/java/dev/caskeleton/adapter/outbound/httpclient/webclient/FirstByteRetryBoundaryTest.java 64
|
|
6541bc4bffaa675074e3886c12fa6e1f3b48384d test/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveSseGatewayTest.java 101
|
|
eb13dd777f8beedb48057001c2fe7c6ebd4194b4 test/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveStreamingLifecycleTest.java 121
|
|
|
|
main: 36 test: 10
|
|
=== 8.1 BoundedDataBufferFlux의 두 연산자가 실제로 하는 일 ===
|
|
$ grep -n 'doOnCancel\|onErrorResume\|doOnDiscard\|doOnNext' adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/BoundedDataBufferFlux.java
|
|
25: .doOnNext(
|
|
30: .doOnDiscard(DataBuffer.class, DataBufferUtils::release)
|
|
31: .doOnCancel(() -> {})
|
|
32: .onErrorResume(
|
|
35: // discarded through doOnDiscard above.
|
|
exit=0
|
|
|
|
$ git grep -rn 'BoundedDataBufferFlux' -- 'adapter/outbound/httpclient/src' | grep -v 'BoundedDataBufferFlux.java'
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveStreamingGateway.java:102: return BoundedDataBufferFlux.bound(
|
|
exit=0
|
|
|
|
=== 8.2 ResponseSizeLimiter의 UNBOUND 메타데이터 생성자 사용처 ===
|
|
$ grep -n 'UNBOUND\|public ResponseSizeLimiter' adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/ResponseSizeLimiter.java
|
|
15: private static final HttpFailureMetadata UNBOUND =
|
|
25: public ResponseSizeLimiter(long maxWireBytes, long maxDecodedBytes) {
|
|
26: this(maxWireBytes, maxDecodedBytes, UNBOUND);
|
|
29: public ResponseSizeLimiter(
|
|
exit=0
|
|
|
|
$ git grep -rn 'new ResponseSizeLimiter(' -- 'adapter/outbound/httpclient/src'
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingAttemptExecutor.java:52: new ResponseSizeLimiter(
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingStreamingGateway.java:60: new ResponseSizeLimiter(
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveStreamingGateway.java:69: new ResponseSizeLimiter(
|
|
adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingResponseMapperTest.java:59: ResponseSizeLimiter limiter = new ResponseSizeLimiter(10, 20);
|
|
adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/ResponseSizeLimiterTest.java:16: ResponseSizeLimiter limiter = new ResponseSizeLimiter(10, 100);
|
|
adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/ResponseSizeLimiterTest.java:21: ResponseSizeLimiter decodedLimiter = new ResponseSizeLimiter(100, 10);
|
|
adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/restclient/ResponseSizeLimiterTest.java:29: ResponseSizeLimiter limiter = new ResponseSizeLimiter(4, 1024);
|
|
exit=0
|
|
|
|
=== 8.3 리다이렉트 hop 상한이 실제로 강제되는가 ===
|
|
$ grep -n 'for (int hop' -A 3 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingRedirectCoordinator.java
|
|
77: for (int hop = 0; isRedirect(response.status()); hop++) {
|
|
78- Optional<String> location = response.firstHeader("Location");
|
|
79- if (location.isEmpty()) {
|
|
80- return response;
|
|
exit=0
|
|
|
|
$ git grep -n 'maxHops' -- 'adapter/outbound/httpclient/src/main'
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/profile/ClientProfileValidator.java:71: if (redirect.maxHops() == 0) {
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/profile/RedirectSettings.java:7:public record RedirectSettings(boolean enabled, int maxHops, boolean allowCrossOrigin) {
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/profile/RedirectSettings.java:12: if (maxHops < 0) {
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectEvaluator.java:16: if (context.hop() >= context.policy().maxHops()) {
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectPolicy.java:18: boolean enabled, int maxHops, boolean allowCrossOrigin, boolean callerManaged) {
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectPolicy.java:23: public RedirectPolicy(boolean enabled, int maxHops, boolean allowCrossOrigin) {
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectPolicy.java:24: this(enabled, maxHops, allowCrossOrigin, false);
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/RedirectPolicy.java:38: settings.enabled(), settings.maxHops(), settings.allowCrossOrigin(), false);
|
|
exit=0
|
|
|
|
=== 8.4 게이트웨이 test ===
|
|
$ git ls-files 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/restclient' 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/webclient' | xargs -n1 basename
|
|
BlockingRedirectCoordinatorTest.java
|
|
BlockingResponseMapperTest.java
|
|
BlockingStreamingLifecycleTest.java
|
|
DefaultGenericHttpGatewayTest.java
|
|
ResponseSizeLimiterTest.java
|
|
BlockHoundEventLoopTest.java
|
|
DefaultReactiveHttpGatewayTest.java
|
|
FirstByteRetryBoundaryTest.java
|
|
ReactiveSseGatewayTest.java
|
|
ReactiveStreamingLifecycleTest.java
|
|
exit=0
|
|
|
|
$ git grep -h ' void [a-z]' -- 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/restclient' 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/webclient' | sed 's/^ *//'
|
|
void refusesRedirectsWhenTheProfileDisablesThem() throws Exception {
|
|
void followsABoundedSameOriginHopWhenEnabled() throws Exception {
|
|
void stopsAtTheConfiguredHopLimit() throws Exception {
|
|
void aHopIsRejectedWhenTheProfileAllowlistExcludesIt() throws Exception {
|
|
void aSeeOtherHopDropsTheRequestBody() throws Exception {
|
|
void mapsProblemJsonWithoutTrustingBodyStatus() {
|
|
void dropsExtensionsThatAreNotAllowlisted() {
|
|
void treatsANonProblemContentTypeAsAnEmptyProblem() {
|
|
void survivesAnUnparseableProblemDocument() {
|
|
void abortsWhenDecodedBytesExceedLimit() {
|
|
void closeReturnsConnectionAfterPartialRead() throws Exception {
|
|
void validatesStatusBeforeDeliveringAnyBodyByte() throws Exception {
|
|
void enforcesTheWireBudgetWhileStreaming() throws Exception {
|
|
void closeIsIdempotent() {
|
|
void expandsRelativeTemplateAndReturnsTypedResult() throws Exception {
|
|
void worksIdenticallyOnTheJdkTransport() throws Exception {
|
|
void rejectsAnAbsoluteUriFromTheCaller() throws Exception {
|
|
void mapsProblemResponsesToStableExceptions() throws Exception {
|
|
void canReturnANonSuccessStatusAsAResultWhenThePolicySaysSo() throws Exception {
|
|
void throwsRemoteErrorWithoutAProblemDocument() throws Exception {
|
|
void recordsSeparateLogicalAndAttemptMetrics() throws Exception {
|
|
void refusesAnUnregisteredProfile() throws Exception {
|
|
void enforcesTheProfileResponseSizeLimit() throws Exception {
|
|
void boundsWireAndDecodedBytesIndependently() {
|
|
void failsWhileReadingRatherThanAfterBuffering() throws IOException {
|
|
void theDetectorItselfCatchesABlockingCallOnANonBlockingScheduler() {
|
|
void theReactiveGatewayCompletesWithoutBlockingAnEventLoop() throws Exception {
|
|
void theReactiveRetryBackoffDoesNotBlockAnEventLoop() throws Exception {
|
|
void returnsTypedResultWithoutBlocking() throws Exception {
|
|
void appliesTheSameTargetPolicyAsTheBlockingGateway() throws Exception {
|
|
void producesTheSameStableFailureTypeForANonSuccessStatus() throws Exception {
|
|
void releasesTheRuntimeLeaseAfterCompletion() throws Exception {
|
|
void theGuardLatchesExactlyOnce() {
|
|
void doesNotRetryAfterFirstBufferWasDelivered() {
|
|
void aPartialResponseNotYetDeliveredMayStillBeRetriedForASafeOperation() {
|
|
void multipartReplayabilityFollowsTheWeakestPart() {
|
|
void decodesEventsFromTheStream() throws Exception {
|
|
void closesSilentStreamAtStreamingIdleTimeout() throws Exception {
|
|
void setupAndStreamingBudgetsAreSeparateSettings() {
|
|
void lastEventIdIsOptInAndOffByDefault() {
|
|
void cancellationReleasesTheConnectionForTheNextCall() throws Exception {
|
|
void statusIsValidatedBeforeAnyBufferIsEmitted() throws Exception {
|
|
void marksTheFirstByteBoundaryWhileStreaming() throws Exception {
|
|
exit=0
|
|
|