fix(idempotency): replace ContentCachingRequestWrapper with re-readable CachedBodyHttpServletRequest
ContentCachingRequestWrapper только cache'ит body ПОСЛЕ того как handler прочитает getInputStream(). Если filter читает body первым (для hash), handler потом получает 'Required request body is missing' — буфер пустой. CachedBodyHttpServletRequest при construction читает body в byte[] и getInputStream() возвращает свежий ByteArrayInputStream при каждом вызове. Filter и handler читают одни и те же байты независимо.
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
package cloud.nstart.terravault.ordinis.restapi.idempotency;
|
||||
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Кэширует тело запроса в памяти, чтобы его можно было прочитать несколько раз
|
||||
* (для idempotency hash + handler-аргумента). В отличие от
|
||||
* {@link org.springframework.web.util.ContentCachingRequestWrapper}, отдает
|
||||
* свежий InputStream при каждом вызове {@link #getInputStream()}.
|
||||
*/
|
||||
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
|
||||
|
||||
private final byte[] cachedBody;
|
||||
|
||||
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
|
||||
super(request);
|
||||
InputStream is = request.getInputStream();
|
||||
this.cachedBody = StreamUtils.copyToByteArray(is);
|
||||
}
|
||||
|
||||
public byte[] getCachedBody() {
|
||||
return cachedBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() {
|
||||
return new CachedServletInputStream(cachedBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() {
|
||||
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static class CachedServletInputStream extends ServletInputStream {
|
||||
private final ByteArrayInputStream buf;
|
||||
|
||||
CachedServletInputStream(byte[] data) {
|
||||
this.buf = new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
@Override public boolean isFinished() { return buf.available() == 0; }
|
||||
@Override public boolean isReady() { return true; }
|
||||
@Override public void setReadListener(ReadListener listener) { /* no-op */ }
|
||||
@Override public int read() { return buf.read(); }
|
||||
@Override public int read(byte[] b, int off, int len) { return buf.read(b, off, len); }
|
||||
}
|
||||
}
|
||||
+2
-6
@@ -12,7 +12,6 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -71,11 +70,8 @@ public class IdempotencyFilter extends OncePerRequestFilter {
|
||||
return;
|
||||
}
|
||||
|
||||
ContentCachingRequestWrapper wrappedReq = new ContentCachingRequestWrapper(request);
|
||||
// Need to read body to compute hash; do it after first content read. Spring caches via
|
||||
// ContentCachingRequestWrapper after handler reads, so we trigger the read manually.
|
||||
wrappedReq.getInputStream().readAllBytes(); // populate cache
|
||||
byte[] body = wrappedReq.getContentAsByteArray();
|
||||
CachedBodyHttpServletRequest wrappedReq = new CachedBodyHttpServletRequest(request);
|
||||
byte[] body = wrappedReq.getCachedBody();
|
||||
String hash = service.hashRequest(body);
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user