From 547f4fb13d7d2e5594866853402a430a62cbe332 Mon Sep 17 00:00:00 2001 From: "wrossmorrow-pi-bot[bot]" <346594324+wrossmorrow-pi-bot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:27:32 +0000 Subject: [PATCH] fix: align dedup example with upstream end-of-stream semantics --- .../processors/DedupRequestProcessor.java | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/github/wrossmorrow/extproc/processors/DedupRequestProcessor.java b/src/main/java/com/github/wrossmorrow/extproc/processors/DedupRequestProcessor.java index f8fa1b9..04cc856 100644 --- a/src/main/java/com/github/wrossmorrow/extproc/processors/DedupRequestProcessor.java +++ b/src/main/java/com/github/wrossmorrow/extproc/processors/DedupRequestProcessor.java @@ -4,6 +4,10 @@ import com.github.wrossmorrow.extproc.RequestContext; import com.github.wrossmorrow.extproc.RequestProcessor; import com.github.wrossmorrow.extproc.RequestProcessorHealthManager; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; @@ -30,14 +34,18 @@ public void shutdown() { } public void processRequestHeaders(RequestContext ctx, Map headers) { - if (ctx.getRequestHeaders().containsKey("x-extproc-request-digest")) { - String digest = ctx.getRequestHeaders().get("x-extproc-request-digest"); + if (!isDedupable(ctx)) { + return; + } + if (ctx.streamComplete()) { + String digest = sha256Hex(ctx.getMethod() + ":" + ctx.getPath()); + ctx.setValue("digest", digest); + ctx.addHeader("x-extproc-request-digest", digest); if (inflight.containsKey(digest)) { - String requestId = inflight.get(digest); ctx.cancelRequest( 409, "{\"message\":\"Duplicate request already in flight\",\"requestId\":\"" - + requestId + + inflight.get(digest) + "\"}"); return; } @@ -45,23 +53,69 @@ public void processRequestHeaders(RequestContext ctx, Map header } } - public void processRequestBody(RequestContext ctx, String body) {} + public void processRequestBody(RequestContext ctx, String body) { + if (!isDedupable(ctx)) { + return; + } + if (ctx.streamComplete()) { + String digest = sha256Hex(ctx.getMethod() + ":" + ctx.getPath() + ":" + body); + ctx.setValue("digest", digest); + ctx.addHeader("x-extproc-request-digest", digest); + if (inflight.containsKey(digest)) { + ctx.cancelRequest( + 409, + "{\"message\":\"Duplicate request already in flight\",\"requestId\":\"" + + inflight.get(digest) + + "\"}"); + return; + } + inflight.put(digest, ctx.getRequestId()); + } + } public void processRequestTrailers(RequestContext ctx, Map trailers) {} public void processResponseHeaders(RequestContext ctx, Map headers) { if (ctx.streamComplete()) { - String digest = ctx.getRequestHeaders().get("x-extproc-request-digest"); - inflight.remove(digest); + String digest = (String) ctx.getValue("digest"); + if (digest != null) { + inflight.remove(digest); + } } } public void processResponseBody(RequestContext ctx, String body) { if (ctx.streamComplete()) { - String digest = ctx.getRequestHeaders().get("x-extproc-request-digest"); - inflight.remove(digest); + String digest = (String) ctx.getValue("digest"); + if (digest != null) { + inflight.remove(digest); + } } } public void processResponseTrailers(RequestContext ctx, Map trailers) {} + + private static boolean isDedupable(RequestContext ctx) { + String method = ctx.getMethod(); + return "PUT".equals(method) || "POST".equals(method) || "PATCH".equals(method); + } + + private static String sha256Hex(String input) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] d = md.digest(input.getBytes(StandardCharsets.UTF_8)); + return hexEncode(d); + } catch (NoSuchAlgorithmException e) { + return ""; + } + } + + private static String hexEncode(final byte[] messageDigest) { + BigInteger bigint = new BigInteger(1, messageDigest); + String hexText = bigint.toString(16); + while (hexText.length() < 32) { + hexText = "0".concat(hexText); + } + return hexText; + } }