Я пытаюсь получить доступ к двум параметрам HTTP-запроса в фильтре сервлета Java, здесь ничего нового, но с удивлением обнаружил, что параметры уже были использованы! Из-за этого он больше не доступен в цепочке фильтров.
Кажется, что это происходит только тогда, когда параметры входят в тело запроса POST (например, отправка формы).
Есть ли способ прочитать параметры и НЕ использовать их?
Пока я нашел только эту ссылку: Фильтр сервлетов с использованием request.getParameter теряет данные формы .
Благодарность!
Ответы:
В качестве альтернативы, альтернативный способ решения этой проблемы - не использовать цепочку фильтров и вместо этого создать собственный компонент-перехватчик, возможно, используя аспекты, которые могут работать с проанализированным телом запроса. Это также, вероятно, будет более эффективным, поскольку вы только конвертируете запрос
InputStream
в свой собственный объект модели.Однако я по-прежнему считаю разумным читать тело запроса более одного раза, особенно когда запрос проходит через цепочку фильтров. Я обычно использую цепочки фильтров для определенных операций, которые я хочу сохранить на уровне HTTP, независимо от компонентов службы.
Как было предложено Уиллом Хартунгом, я добился этого путем расширения
HttpServletRequestWrapper
, использования запросаInputStream
и, по сути, кэширования байтов.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(); } } }
Теперь тело запроса можно прочитать более одного раза, обернув исходный запрос перед его передачей через цепочку фильтров:
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); } }
Это решение также позволит вам несколько раз прочитать тело запроса с помощью
getParameterXXX
методов, потому что это базовый вызовgetInputStream()
, который, конечно, будет читать кешированный запросInputStream
.редактировать
Для более новой версии
ServletInputStream
интерфейса. Вам необходимо предоставить реализацию еще нескольких методов, напримерisReady
,setReadListener
и т.д. Обратитесь к этому вопросу, как указано в комментарии ниже.источник
getInputStream
вызывается в моей оболочке, поскольку этоServletRequest
экземпляр, который я передаю в цепочку фильтров. Если вы все еще сомневаетесь, прочтите исходный кодServletRequestWrapper
иServletRequest
интерфейс.isReady()
.isFinished()
иsetReadListener()
иметь дело с неблокирующим вводом-выводом, который должен быть реализован. Я думаю, что ReadListener можно оставить пустым, но я не знаю, что делатьisFinished()
и / илиisReady()
.Я знаю, что опаздываю, но этот вопрос все еще был актуален для меня, и этот пост SO был одним из самых популярных в Google. Я собираюсь опубликовать свое решение в надежде, что кто-то еще может сэкономить пару часов.
В моем случае мне нужно было регистрировать все запросы и ответы с их телами. Используя Spring Framework, ответ на самом деле довольно прост, просто используйте ContentCachingRequestWrapper и 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 } } }
источник
requestBody
и другиеresponseBody
были пустыми строкамиchain.doFilter(request, response);
вместоchain.doFilter(requestWrapper, responseWrapper);
ContentCaching*Wrapper
классах имеют дорогую цену потребляя входной поток , так что «кэширование» осуществляются с помощью метода ,getContentAsByteArray
но этот класс не кэшировать входной поток , который может быть необходим другими фильтрами в цепочке фильтров (это мой случай использования). Imho, это неожиданноеAbstractRequestLoggingFilter
из Spring, где большая часть работы уже сделана Spring, и вам нужно только переопределить 1 или 2 простых метода.spring-web-4.3.12.RELEASE
. Когда я проверил источник, я обнаружил, что переменнаяcachedContent
используется для хранения различного содержимого, такого как параметры запроса и inputStream запроса. Пусто, если звонить вgetContentAsByteArray()
одиночку. Для получения тела запроса необходимо позвонитьgetInputStream()
. Но опять же, это сделает inputStream недоступным для других фильтров и обработчика.Приведенные выше ответы были очень полезны, но в моем опыте все еще были проблемы. В сервлете Tomcat 7 3.0 также пришлось перезаписать getParamter и getParamterValues. Решение здесь включает в себя как параметры запроса получения, так и тело сообщения. Это позволяет легко получить необработанную строку.
Как и другие решения, он использует Apache commons-io и Googles Guava.
В этом решении методы getParameter * не генерируют IOException, но они используют super.getInputStream () (для получения тела), который может вызывать IOException. Я ловлю его и бросаю runtimeException. Это не очень приятно.
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(); } }
источник
decode(getPostBodyAsString(), result);
вgetParameterMap()
? Это создает параметр с ключом = тело запроса и значение = ноль, что довольно странно.super.getParameterMap()
свойgetParameterMap
? Что в<String, String[]>
любом случае даст вам карту .Единственный способ - использовать весь входной поток самостоятельно в фильтре, взять из него то, что вы хотите, а затем создать новый InputStream для прочитанного вами содержимого и поместить этот InputStream в ServletRequestWrapper (или HttpServletRequestWrapper).
Обратной стороной является то, что вам придется анализировать полезную нагрузку самостоятельно, стандарт не делает эту возможность доступной для вас.
Дополнения -
Как я уже сказал, вам нужно посмотреть HttpServletRequestWrapper.
В фильтре вы продолжаете, вызывая FilterChain.doFilter (запрос, ответ).
Для тривиальных фильтров запрос и ответ такие же, как те, которые передаются фильтру. Это не должно быть так. Вы можете заменить их своими собственными запросами и / или ответами.
HttpServletRequestWrapper специально разработан для этого. Вы передаете ему исходный запрос, а затем можете перехватывать все вызовы. Вы создаете свой собственный подкласс этого и заменяете метод getInputStream одним из ваших собственных. Вы не можете изменить входной поток исходного запроса, поэтому вместо этого у вас есть эта оболочка и вы возвращаете свой собственный входной поток.
Самый простой случай - использовать исходный входной поток запросов в байтовый буфер, делать с ним все, что угодно, а затем создавать новый ByteArrayInputStream из этого буфера. Это то, что возвращается в вашей оболочке, которая передается методу FilterChain.doFilter.
Вам нужно будет создать подкласс ServletInputStream и создать другую оболочку для своего ByteArrayInputStream, но это тоже не имеет большого значения.
источник
У меня тоже была такая же проблема, и я считаю, что приведенный ниже код более простой и работает для меня,
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())); } }
в классе java фильтра,
HttpServletRequest properRequest = ((HttpServletRequest) req); MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(properRequest); req = wrappedRequest; inputJson = IOUtils.toString(req.getReader()); System.out.println("body"+inputJson);
Пожалуйста, дайте мне знать, если у вас возникнут вопросы
источник
Итак, это в основном ответ Лати, НО обновленный для новых требований для ServletInputStream.
А именно (для ServletInputStream) необходимо реализовать:
public abstract boolean isFinished(); public abstract boolean isReady(); public abstract void setReadListener(ReadListener var1);
Это отредактированный объект Лати
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())); } }
и где-то (??) я нашел это (это первоклассный класс, который имеет дело с "дополнительными" методами.
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; } } };
В конечном итоге я просто пытался регистрировать запросы. И приведенные выше фрагменты, соединенные вместе франкенштейном, помогли мне создать нижеследующее.
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; } }
Пожалуйста, отнеситесь к этому коду с недоверием.
САМЫЙ важный «тест» - это то, работает ли POST с полезной нагрузкой. Это то, что выявит проблемы "двойного чтения".
псевдо пример кода
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; } }
Вы можете заменить MyCustomObject на обычный старый Object, если хотите просто протестировать.
Этот ответ основан на нескольких различных сообщениях и примерах SOF ... но потребовалось время, чтобы собрать все воедино, поэтому я надеюсь, что это поможет будущему читателю.
Пожалуйста, проголосуйте за ответ Лати перед моим. Без этого я бы не продвинулся так далеко.
Ниже приведено одно / некоторые из исключений, которые я получил во время работы над этим.
Похоже, что некоторые из мест, которые я "позаимствовал", находятся здесь:
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
источник
Spring имеет встроенную поддержку для этого с
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; }
К сожалению, вы по-прежнему не сможете прочитать полезную нагрузку непосредственно из запроса, но параметр сообщения String будет включать полезную нагрузку, поэтому вы можете получить ее оттуда следующим образом:
String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));
источник
Просто перезапись
getInputStream()
в моем случае не работала. Моя реализация сервера, кажется, анализирует параметры без вызова этого метода. Я не нашел другого способа, но также повторно реализовал все четыре метода getParameter *. Вот кодgetParameterMap
(используются Apache Http Client и Google Guava библиотека):@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; }
источник
Если у вас есть контроль над запросом, вы можете установить тип контента на двоичный / октетный поток. . Это позволяет запрашивать параметры без использования входного потока.
Однако это может быть специфическим для некоторых серверов приложений. Я тестировал только tomcat, причал, похоже, ведет себя так же, в соответствии с qaru.site/questions/435 / ... / 11434646/957103 .
источник
Метод getContentAsByteArray () класса Spring ContentCachingRequestWrapper читает тело несколько раз, но методы getInputStream () и getReader () того же класса не читают тело несколько раз:
"Этот класс кэширует тело запроса, используя InputStream. Если мы читаем InputStream в одном из фильтров, то другие последующие фильтры в цепочке фильтров больше не смогут его читать. Из-за этого ограничения этот класс не подходит для всех ситуации ".
В моем случае более общее решение, которое решило эту проблему, заключалось в добавлении следующих трех классов в мой загрузочный проект Spring (и необходимых зависимостей для файла 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); } }
Я также добавил в 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>
Учебный и полный исходный код находится здесь: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times
источник
вы можете использовать цепочку фильтров сервлетов, но вместо этого использовать исходную, вы можете создать свой собственный запрос. yourownrequests extends HttpServletRequestWrapper.
источник
Прежде всего, мы не должны читать параметры в фильтре. Обычно заголовки читаются в фильтре для выполнения нескольких задач аутентификации. Сказав, что можно полностью прочитать тело HttpRequest в фильтре или перехватчике, используя CharStreams:
Это никак не влияет на последующие чтения.
источник
request.getReader()
вернет читатель, который будет содержать только пустую строку при последующих чтениях.