Saya mencoba mengakses dua parameter permintaan http dalam filter Java Servlet, tidak ada yang baru di sini, tetapi terkejut menemukan bahwa parameter tersebut telah dikonsumsi! Karena itu, filter tidak lagi tersedia di rantai filter.
Tampaknya ini hanya terjadi ketika parameter datang dalam badan permintaan POST (misalnya, formulir yang dikirim).
Apakah ada cara untuk membaca parameter dan TIDAK mengkonsumsinya?
Sejauh ini saya hanya menemukan referensi ini: Filter Servlet menggunakan request.getParameter kehilangan data Formulir .
Terima kasih!
Jawaban:
Selain itu, cara alternatif untuk mengatasi masalah ini adalah dengan tidak menggunakan rantai filter dan malah membangun komponen pencegat Anda sendiri, mungkin menggunakan aspek, yang dapat beroperasi pada isi permintaan yang diurai. Ini juga mungkin akan lebih efisien karena Anda hanya mengonversi permintaan
InputStream
menjadi objek model Anda sendiri satu kali.Namun, menurut saya masuk akal untuk ingin membaca isi permintaan lebih dari sekali, terutama saat permintaan berpindah melalui rantai filter. Saya biasanya akan menggunakan rantai filter untuk operasi tertentu yang ingin saya pertahankan di lapisan HTTP, dipisahkan dari komponen layanan.
Seperti yang disarankan oleh Will Hartung, saya mencapai ini dengan memperluas
HttpServletRequestWrapper
, mengonsumsi permintaan,InputStream
dan pada dasarnya meng-cache byte.public class MultiReadHttpServletRequest extends HttpServletRequestWrapper { private ByteArrayOutputStream cachedBytes; public MultiReadHttpServletRequest(HttpServletRequest request) { super(request); } @Override public ServletInputStream getInputStream() throws IOException { if (cachedBytes == null) cacheInputStream(); return new CachedServletInputStream(); } @Override public BufferedReader getReader() throws IOException{ return new BufferedReader(new InputStreamReader(getInputStream())); } private void cacheInputStream() throws IOException { /* Cache the inputstream in order to read it multiple times. For * convenience, I use apache.commons IOUtils */ cachedBytes = new ByteArrayOutputStream(); IOUtils.copy(super.getInputStream(), cachedBytes); } /* An inputstream which reads the cached request body */ public class CachedServletInputStream extends ServletInputStream { private ByteArrayInputStream input; public CachedServletInputStream() { /* create a new input stream from the cached request body */ input = new ByteArrayInputStream(cachedBytes.toByteArray()); } @Override public int read() throws IOException { return input.read(); } } }
Sekarang badan permintaan bisa dibaca lebih dari sekali dengan membungkus permintaan asli sebelum meneruskannya melalui rantai filter:
public class MyFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { /* wrap the request in order to read the inputstream multiple times */ MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request); /* here I read the inputstream and do my thing with it; when I pass the * wrapped request through the filter chain, the rest of the filters, and * request handlers may read the cached inputstream */ doMyThing(multiReadRequest.getInputStream()); //OR anotherUsage(multiReadRequest.getReader()); chain.doFilter(multiReadRequest, response); } }
Solusi ini juga akan memungkinkan Anda membaca isi permintaan beberapa kali melalui
getParameterXXX
metode karena panggilan dasarnya adalahgetInputStream()
, yang tentu saja akan membaca permintaan yang di-cacheInputStream
.Sunting
Untuk versi
ServletInputStream
antarmuka yang lebih baru . Anda perlu memberikan implementasi beberapa metode lagi sepertiisReady
,setReadListener
dll. Lihat pertanyaan ini seperti yang diberikan dalam komentar di bawah.sumber
getInputStream
dipanggil pada pembungkus saya karena ini adalahServletRequest
contoh yang saya berikan ke rantai filter. Jika Anda masih ragu, baca kode sumberServletRequestWrapper
danServletRequest
antarmukanya.isReady()
.isFinished()
dansetReadListener()
untuk menangani IO non-pemblokiran yang harus diterapkan. Saya pikir ReadListener dapat dikosongkan, tetapi tidak yakin apa yang harus dilakukanisFinished()
dan / atauisReady()
.Saya tahu saya terlambat, tetapi pertanyaan ini masih relevan untuk saya dan posting SO ini adalah salah satu hit teratas di Google. Saya akan melanjutkan dan memposting solusi saya dengan harapan orang lain dapat menghemat beberapa jam.
Dalam kasus saya, saya perlu mencatat semua permintaan dan tanggapan dengan tubuh mereka. Menggunakan Spring Framework jawabannya sebenarnya cukup sederhana, cukup gunakan ContentCachingRequestWrapper dan ContentCachingResponseWrapper .
import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingResponseWrapper; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class LoggingFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request); ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response); try { chain.doFilter(requestWrapper, responseWrapper); } finally { String requestBody = new String(requestWrapper.getContentAsByteArray()); String responseBody = new String(responseWrapper.getContentAsByteArray()); // Do not forget this line after reading response content or actual response will be empty! responseWrapper.copyBodyToResponse(); // Write request and response body, headers, timestamps etc. to log files } } }
sumber
requestBody
danresponseBody
merupakan string kosongchain.doFilter(request, response);
alih - alihchain.doFilter(requestWrapper, responseWrapper);
ContentCaching*Wrapper
kelas memiliki harga mahal mengkonsumsi input stream sehingga "caching" dilakukan melalui metodegetContentAsByteArray
tetapi kelas ini tidak caching aliran masukan yang mungkin dibutuhkan oleh filter lainnya dalam rantai filter (yang menggunakan kasus saya). Imho, ini adalah perilaku yang tidak diharapkan dari kelas cache konten, oleh karena itu saya mengangkat peningkatan ini di tim musim semi jira.spring.io/browse/SPR-16028AbstractRequestLoggingFilter
dari Spring, di mana sebagian besar pekerjaan telah dilakukan oleh Spring dan Anda hanya perlu mengganti 1 atau 2 metode sederhana.spring-web-4.3.12.RELEASE
. Ketika saya memeriksa sumber, saya menemukan variabelcachedContent
digunakan untuk menyimpan berbagai konten seperti parameter permintaan dan permintaan inputStream. Kosong jika AndagetContentAsByteArray()
hanya menelepon . Untuk mendapatkan badan permintaan, Anda harus menelepongetInputStream()
. Tapi sekali lagi, ini akan membuat inputStream tidak tersedia untuk filter dan penangan lain.Jawaban di atas sangat membantu, tetapi masih ada beberapa masalah dalam pengalaman saya. Pada tomcat 7 servlet 3.0, getParamter dan getParamterValues juga harus ditimpa. Solusi di sini mencakup parameter get-query dan post-body. Ini memungkinkan untuk mendapatkan string mentah dengan mudah.
Seperti solusi lainnya, ia menggunakan Apache commons-io dan Googles Guava.
Dalam solusi ini, metode getParameter * tidak menampilkan IOException tetapi menggunakan super.getInputStream () (untuk mendapatkan isi) yang mungkin menampilkan IOException. Saya menangkapnya dan membuang runtimeException. Ini tidak terlalu bagus.
import com.google.common.collect.Iterables; import com.google.common.collect.ObjectArrays; import org.apache.commons.io.IOUtils; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.entity.ContentType; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * Purpose of this class is to make getParameter() return post data AND also be able to get entire * body-string. In native implementation any of those two works, but not both together. */ public class MultiReadHttpServletRequest extends HttpServletRequestWrapper { public static final String UTF8 = "UTF-8"; public static final Charset UTF8_CHARSET = Charset.forName(UTF8); private ByteArrayOutputStream cachedBytes; private Map<String, String[]> parameterMap; public MultiReadHttpServletRequest(HttpServletRequest request) { super(request); } public static void toMap(Iterable<NameValuePair> inputParams, Map<String, String[]> toMap) { for (NameValuePair e : inputParams) { String key = e.getName(); String value = e.getValue(); if (toMap.containsKey(key)) { String[] newValue = ObjectArrays.concat(toMap.get(key), value); toMap.remove(key); toMap.put(key, newValue); } else { toMap.put(key, new String[]{value}); } } } @Override public ServletInputStream getInputStream() throws IOException { if (cachedBytes == null) cacheInputStream(); return new CachedServletInputStream(); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } private void cacheInputStream() throws IOException { /* Cache the inputStream in order to read it multiple times. For * convenience, I use apache.commons IOUtils */ cachedBytes = new ByteArrayOutputStream(); IOUtils.copy(super.getInputStream(), cachedBytes); } @Override public String getParameter(String key) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(key); return values != null && values.length > 0 ? values[0] : null; } @Override public String[] getParameterValues(String key) { Map<String, String[]> parameterMap = getParameterMap(); return parameterMap.get(key); } @Override public Map<String, String[]> getParameterMap() { if (parameterMap == null) { Map<String, String[]> result = new LinkedHashMap<String, String[]>(); decode(getQueryString(), result); decode(getPostBodyAsString(), result); parameterMap = Collections.unmodifiableMap(result); } return parameterMap; } private void decode(String queryString, Map<String, String[]> result) { if (queryString != null) toMap(decodeParams(queryString), result); } private Iterable<NameValuePair> decodeParams(String body) { Iterable<NameValuePair> params = URLEncodedUtils.parse(body, UTF8_CHARSET); try { String cts = getContentType(); if (cts != null) { ContentType ct = ContentType.parse(cts); if (ct.getMimeType().equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType())) { List<NameValuePair> postParams = URLEncodedUtils.parse(IOUtils.toString(getReader()), UTF8_CHARSET); params = Iterables.concat(params, postParams); } } } catch (IOException e) { throw new IllegalStateException(e); } return params; } public String getPostBodyAsString() { try { if (cachedBytes == null) cacheInputStream(); return cachedBytes.toString(UTF8); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } /* An inputStream which reads the cached request body */ public class CachedServletInputStream extends ServletInputStream { private ByteArrayInputStream input; public CachedServletInputStream() { /* create a new input stream from the cached request body */ input = new ByteArrayInputStream(cachedBytes.toByteArray()); } @Override public int read() throws IOException { return input.read(); } } @Override public String toString() { String query = dk.bnr.util.StringUtil.nullToEmpty(getQueryString()); StringBuilder sb = new StringBuilder(); sb.append("URL='").append(getRequestURI()).append(query.isEmpty() ? "" : "?" + query).append("', body='"); sb.append(getPostBodyAsString()); sb.append("'"); return sb.toString(); } }
sumber
decode(getPostBodyAsString(), result);
digetParameterMap()
? Itu membuat parameter dengan key = request body dan value = null, yang cukup aneh.super.getParameterMap()
AndagetParameterMap
? Yang akan memberi Anda peta<String, String[]>
.Satu-satunya cara adalah Anda menggunakan sendiri seluruh aliran input di filter, mengambil apa yang Anda inginkan darinya, lalu membuat InputStream baru untuk konten yang Anda baca, dan memasukkan InputStream itu ke ServletRequestWrapper (atau HttpServletRequestWrapper).
Sisi negatifnya adalah Anda harus mengurai sendiri muatannya, standar tidak membuat kemampuan itu tersedia untuk Anda.
Tambahan -
Seperti yang saya katakan, Anda perlu melihat HttpServletRequestWrapper.
Dalam sebuah filter, Anda melanjutkan dengan memanggil FilterChain.doFilter (request, response).
Untuk filter sepele, permintaan dan respons sama dengan yang diteruskan ke filter. Tidak harus demikian. Anda dapat menggantinya dengan permintaan dan / atau tanggapan Anda sendiri.
HttpServletRequestWrapper dirancang khusus untuk memfasilitasi ini. Anda meneruskannya pada permintaan awal, dan kemudian Anda dapat menghentikan semua panggilan. Anda membuat subkelas Anda sendiri untuk ini, dan mengganti metode getInputStream dengan salah satu milik Anda. Anda tidak dapat mengubah aliran masukan dari permintaan asli, jadi Anda memiliki pembungkus ini dan mengembalikan aliran masukan Anda sendiri.
Kasus paling sederhana adalah menggunakan aliran input permintaan asli ke buffer byte, melakukan sihir apa pun yang Anda inginkan, lalu membuat ByteArrayInputStream baru dari buffer itu. Inilah yang dikembalikan di pembungkus Anda, yang diteruskan ke metode FilterChain.doFilter.
Anda harus membuat subkelas ServletInputStream dan membuat pembungkus lain untuk ByteArrayInputStream Anda, tetapi itu juga bukan masalah besar.
sumber
Saya juga mengalami masalah yang sama dan saya yakin kode di bawah ini lebih sederhana dan berfungsi untuk saya,
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper { private String _body; public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException { super(request); _body = ""; BufferedReader bufferedReader = request.getReader(); String line; while ((line = bufferedReader.readLine()) != null){ _body += line; } } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes()); return new ServletInputStream() { public int read() throws IOException { return byteArrayInputStream.read(); } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } }
di kelas filter java,
HttpServletRequest properRequest = ((HttpServletRequest) req); MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(properRequest); req = wrappedRequest; inputJson = IOUtils.toString(req.getReader()); System.out.println("body"+inputJson);
Beri tahu saya jika Anda memiliki pertanyaan
sumber
Jadi ini pada dasarnya adalah jawaban Lathy TAPI diperbarui untuk persyaratan yang lebih baru untuk ServletInputStream.
Yaitu (untuk ServletInputStream), seseorang harus mengimplementasikan:
public abstract boolean isFinished(); public abstract boolean isReady(); public abstract void setReadListener(ReadListener var1);
Ini adalah objek Lathy yang sudah diedit
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; public class RequestWrapper extends HttpServletRequestWrapper { private String _body; public RequestWrapper(HttpServletRequest request) throws IOException { super(request); _body = ""; BufferedReader bufferedReader = request.getReader(); String line; while ((line = bufferedReader.readLine()) != null){ _body += line; } } @Override public ServletInputStream getInputStream() throws IOException { CustomServletInputStream kid = new CustomServletInputStream(_body.getBytes()); return kid; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } }
dan di suatu tempat (??) saya menemukan ini (yang merupakan kelas kelas satu yang berhubungan dengan metode "ekstra".
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; public class CustomServletInputStream extends ServletInputStream { private byte[] myBytes; private int lastIndexRetrieved = -1; private ReadListener readListener = null; public CustomServletInputStream(String s) { try { this.myBytes = s.getBytes("UTF-8"); } catch (UnsupportedEncodingException ex) { throw new IllegalStateException("JVM did not support UTF-8", ex); } } public CustomServletInputStream(byte[] inputBytes) { this.myBytes = inputBytes; } @Override public boolean isFinished() { return (lastIndexRetrieved == myBytes.length - 1); } @Override public boolean isReady() { // This implementation will never block // We also never need to call the readListener from this method, as this method will never return false return isFinished(); } @Override public void setReadListener(ReadListener readListener) { this.readListener = readListener; if (!isFinished()) { try { readListener.onDataAvailable(); } catch (IOException e) { readListener.onError(e); } } else { try { readListener.onAllDataRead(); } catch (IOException e) { readListener.onError(e); } } } @Override public int read() throws IOException { int i; if (!isFinished()) { i = myBytes[lastIndexRetrieved + 1]; lastIndexRetrieved++; if (isFinished() && (readListener != null)) { try { readListener.onAllDataRead(); } catch (IOException ex) { readListener.onError(ex); throw ex; } } return i; } else { return -1; } } };
Pada akhirnya, saya hanya mencoba mencatat permintaan. Dan potongan-potongan frankensteined di atas membantu saya membuat di bawah ini.
import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.Principal; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; //one or the other based on spring version //import org.springframework.boot.autoconfigure.web.ErrorAttributes; import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.WebRequest; import org.springframework.web.filter.OncePerRequestFilter; /** * A filter which logs web requests that lead to an error in the system. */ @Component public class LogRequestFilter extends OncePerRequestFilter implements Ordered { // I tried apache.commons and slf4g loggers. (one or the other in these next 2 lines of declaration */ //private final static org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory.getLog(LogRequestFilter.class); private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(LogRequestFilter.class); // put filter at the end of all other filters to make sure we are processing after all others private int order = Ordered.LOWEST_PRECEDENCE - 8; private ErrorAttributes errorAttributes; @Override public int getOrder() { return order; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String temp = ""; /* for a breakpoint, remove for production/real code */ /* change to true for easy way to comment out this code, remove this if-check for production/real code */ if (false) { filterChain.doFilter(request, response); return; } /* make a "copy" to avoid issues with body-can-only-read-once issues */ RequestWrapper reqWrapper = new RequestWrapper(request); int status = HttpStatus.INTERNAL_SERVER_ERROR.value(); // pass through filter chain to do the actual request handling filterChain.doFilter(reqWrapper, response); status = response.getStatus(); try { Map<String, Object> traceMap = getTrace(reqWrapper, status); // body can only be read after the actual request handling was done! this.getBodyFromTheRequestCopy(reqWrapper, traceMap); /* now do something with all the pieces of information gatherered */ this.logTrace(reqWrapper, traceMap); } catch (Exception ex) { logger.error("LogRequestFilter FAILED: " + ex.getMessage(), ex); } } private void getBodyFromTheRequestCopy(RequestWrapper rw, Map<String, Object> trace) { try { if (rw != null) { byte[] buf = IOUtils.toByteArray(rw.getInputStream()); //byte[] buf = rw.getInputStream(); if (buf.length > 0) { String payloadSlimmed; try { String payload = new String(buf, 0, buf.length, rw.getCharacterEncoding()); payloadSlimmed = payload.trim().replaceAll(" +", " "); } catch (UnsupportedEncodingException ex) { payloadSlimmed = "[unknown]"; } trace.put("body", payloadSlimmed); } } } catch (IOException ioex) { trace.put("body", "EXCEPTION: " + ioex.getMessage()); } } private void logTrace(HttpServletRequest request, Map<String, Object> trace) { Object method = trace.get("method"); Object path = trace.get("path"); Object statusCode = trace.get("statusCode"); logger.info(String.format("%s %s produced an status code '%s'. Trace: '%s'", method, path, statusCode, trace)); } protected Map<String, Object> getTrace(HttpServletRequest request, int status) { Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception"); Principal principal = request.getUserPrincipal(); Map<String, Object> trace = new LinkedHashMap<String, Object>(); trace.put("method", request.getMethod()); trace.put("path", request.getRequestURI()); if (null != principal) { trace.put("principal", principal.getName()); } trace.put("query", request.getQueryString()); trace.put("statusCode", status); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); trace.put("header:" + key, value); } if (exception != null && this.errorAttributes != null) { trace.put("error", this.errorAttributes .getErrorAttributes((WebRequest) new ServletRequestAttributes(request), true)); } return trace; } }
Silakan ambil kode ini dengan sebutir garam.
"Tes" PALING penting adalah jika POST bekerja dengan payload. Inilah yang akan mengungkap masalah "baca ganda".
kode contoh palsu
import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("myroute") public class MyController { @RequestMapping(method = RequestMethod.POST, produces = "application/json") @ResponseBody public String getSomethingExample(@RequestBody MyCustomObject input) { String returnValue = ""; return returnValue; } }
Anda dapat mengganti "MyCustomObject" dengan "Object" biasa jika Anda hanya ingin menguji.
Jawaban ini jujur dari beberapa posting dan contoh SOF yang berbeda .. tapi butuh beberapa saat untuk menyatukan semuanya jadi saya harap ini membantu pembaca di masa mendatang.
Tolong beri suara positif pada jawaban Lathy sebelum jawaban saya. Aku tidak akan bisa sejauh ini tanpanya.
Di bawah ini adalah salah satu / beberapa pengecualian yang saya dapatkan saat mengerjakan ini.
Sepertinya beberapa tempat yang "saya pinjam" ada di sini:
http://slackspace.de/articles/log-request-body-with-spring-boot/
https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java
https://howtodoinjava.com/servlets/httpservletrequestwrapper-example-read-request-body/
https://www.oodlestechnologies.com/blogs/How-to-create-duplicate-object-of-httpServletRequest-object
https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java
sumber
Spring memiliki dukungan bawaan untuk ini dengan
AbstractRequestLoggingFilter
:@Bean public Filter loggingFilter(){ final AbstractRequestLoggingFilter filter = new AbstractRequestLoggingFilter() { @Override protected void beforeRequest(final HttpServletRequest request, final String message) { } @Override protected void afterRequest(final HttpServletRequest request, final String message) { } }; filter.setIncludePayload(true); filter.setIncludeQueryString(false); filter.setMaxPayloadLength(1000000); return filter; }
Sayangnya Anda masih tidak dapat membaca payload langsung dari permintaan, tetapi parameter pesan String akan menyertakan payload sehingga Anda dapat mengambilnya dari sana sebagai berikut:
String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));
sumber
Hanya menimpa
getInputStream()
tidak berhasil dalam kasus saya. Implementasi server saya tampaknya mengurai parameter tanpa memanggil metode ini. Saya tidak menemukan cara lain, tetapi menerapkan ulang keempat metode getParameter * juga. Berikut adalah kodegetParameterMap
(Apache Http Client dan pustaka Google Guava digunakan):@Override public Map<String, String[]> getParameterMap() { Iterable<NameValuePair> params = URLEncodedUtils.parse(getQueryString(), NullUtils.UTF8); try { String cts = getContentType(); if (cts != null) { ContentType ct = ContentType.parse(cts); if (ct.getMimeType().equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType())) { List<NameValuePair> postParams = URLEncodedUtils.parse(IOUtils.toString(getReader()), NullUtils.UTF8); params = Iterables.concat(params, postParams); } } } catch (IOException e) { throw new IllegalStateException(e); } Map<String, String[]> result = toMap(params); return result; } public static Map<String, String[]> toMap(Iterable<NameValuePair> body) { Map<String, String[]> result = new LinkedHashMap<>(); for (NameValuePair e : body) { String key = e.getName(); String value = e.getValue(); if (result.containsKey(key)) { String[] newValue = ObjectArrays.concat(result.get(key), value); result.remove(key); result.put(key, newValue); } else { result.put(key, new String[] {value}); } } return result; }
sumber
Jika Anda memiliki kendali atas permintaan tersebut, Anda dapat mengatur tipe konten ke aliran biner / oktet . Hal ini memungkinkan untuk meminta parameter tanpa menghabiskan aliran input.
Namun, ini mungkin khusus untuk beberapa server aplikasi. Saya hanya menguji tomcat, jetty tampaknya berperilaku dengan cara yang sama menurut https://stackoverflow.com/a/11434646/957103 .
sumber
Metode getContentAsByteArray () dari kelas Spring ContentCachingRequestWrapper membaca isi beberapa kali, tetapi metode getInputStream () dan getReader () dari kelas yang sama tidak membaca isi beberapa kali:
"Kelas ini meng-cache badan permintaan dengan menggunakan InputStream. Jika kita membaca InputStream di salah satu filter, filter berikutnya di rantai filter tidak dapat membacanya lagi. Karena batasan ini, kelas ini tidak cocok di semua situasi. "
Dalam kasus saya, solusi yang lebih umum yang memecahkan masalah ini adalah menambahkan tiga kelas berikut ke proyek boot Spring saya (dan dependensi yang diperlukan ke file pom):
CachedBodyHttpServletRequest.java:
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedBody; public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { super(request); InputStream requestInputStream = request.getInputStream(); this.cachedBody = StreamUtils.copyToByteArray(requestInputStream); } @Override public ServletInputStream getInputStream() throws IOException { return new CachedBodyServletInputStream(this.cachedBody); } @Override public BufferedReader getReader() throws IOException { // Create a reader from cachedContent // and return it ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); return new BufferedReader(new InputStreamReader(byteArrayInputStream)); } }
CachedBodyServletInputStream.java:
public class CachedBodyServletInputStream extends ServletInputStream { private InputStream cachedBodyInputStream; public CachedBodyServletInputStream(byte[] cachedBody) { this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody); } @Override public boolean isFinished() { try { return cachedBodyInputStream.available() == 0; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return false; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { throw new UnsupportedOperationException(); } @Override public int read() throws IOException { return cachedBodyInputStream.read(); } }
ContentCachingFilter.java:
@Order(value = Ordered.HIGHEST_PRECEDENCE) @Component @WebFilter(filterName = "ContentCachingFilter", urlPatterns = "/*") public class ContentCachingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { System.out.println("IN ContentCachingFilter "); CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(httpServletRequest); filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse); } }
Saya juga menambahkan dependensi berikut ke pom:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.0</version> </dependency>
Sebuah tuturial dan kode sumber lengkap ada di sini: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times
sumber
Saya menemukan solusi yang bagus untuk semua format badan permintaan. Saya menguji
application/x-www-form-urlencoded
danapplication/json
keduanya bekerja dengan sangat baik. MasalahContentCachingRequestWrapper
itu dirancang hanya untukx-www-form-urlencoded
badan permintaan, tetapi tidak bekerja dengan misalnya json. Saya menemukan solusi untuk tautan json . Ada masalah yang tidak didukungnyax-www-form-urlencoded
. Saya bergabung dengan keduanya di kode saya:import org.apache.commons.io.IOUtils; import org.springframework.web.util.ContentCachingRequestWrapper; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; public class MyContentCachingRequestWrapper extends ContentCachingRequestWrapper { private byte[] body; public MyContentCachingRequestWrapper(HttpServletRequest request) throws IOException { super(request); super.getParameterMap(); // init cache in ContentCachingRequestWrapper body = super.getContentAsByteArray(); // first option for application/x-www-form-urlencoded if (body.length == 0) { body = IOUtils.toByteArray(super.getInputStream()); // second option for other body formats } } public byte[] getBody() { return body; } @Override public ServletInputStream getInputStream() { return new RequestCachingInputStream(body); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding())); } private static class RequestCachingInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public RequestCachingInputStream(byte[] bytes) { inputStream = new ByteArrayInputStream(bytes); } @Override public int read() throws IOException { return inputStream.read(); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readlistener) { } } }
sumber
Anda dapat menggunakan rantai filter servlet, tetapi sebaliknya menggunakan yang asli, Anda dapat membuat permintaan Anda sendiri yourownrequests extends HttpServletRequestWrapper.
sumber
Pertama-tama kita tidak harus membaca parameter di dalam filter. Biasanya header dibaca di filter untuk melakukan beberapa tugas otentikasi. Karena itu seseorang dapat membaca isi HttpRequest sepenuhnya di Filter atau Interceptor dengan menggunakan CharStreams:
Ini sama sekali tidak mempengaruhi pembacaan berikutnya.
sumber
request.getReader()
akan mengembalikan pembaca yang hanya berisi string kosong pada bacaan berikutnya.