[英]How to read request parameter values Using HandlerInterceptor in spring?
[英]How to read request body in HandlerInterceptor?
我有 Spring Boot,我需要在數據庫中記錄用戶操作,所以我寫了 HandlerInterceptor:
@Component
public class LogInterceptor implements HandlerInterceptor {
@Autovired
private LogUserActionService logUserActionService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws IOException {
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURI();
String queryString = request.getQueryString() != null ? request.getQueryString() : "";
String body = "POST".equalsIgnoreCase(request.getMethod()) ? new BufferedReader(new InputStreamReader(request.getInputStream())).lines().collect(Collectors.joining(System.lineSeparator())) : queryString;
logUserActionService.logUserAction(userName, url, body);
return true;
}
}
但是根據這個答案Get RequestBody and ResponseBody at HandlerInterceptor "RequestBody can be read only once",據我所知,我讀取了輸入 stream 然后 Spring 嘗試做同樣的事情,但是 stream 已經被讀取並且我收到一個錯誤: “缺少所需的請求正文……”
所以我嘗試了不同的方法來制作緩沖輸入 stream 即:
HttpServletRequest httpServletRequest = new ContentCachingRequestWrapper(request);
new BufferedReader(new InputStreamReader(httpServletRequest.getInputStream())).lines().collect(Collectors.joining(System.lineSeparator()))
或者
InputStream bufferedInputStream = new BufferedInputStream(request.getInputStream());
但沒有任何幫助我也嘗試使用
@ControllerAdvice
public class UserActionRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {
但它只有主體,沒有請求信息,如 URL 或請求參數也嘗試使用過濾器,但結果相同。
因此,我需要一種從用戶、URL、參數、正文(如果存在)等請求中獲取信息並將其寫入數據庫的好方法。
您可以使用Filter
來記錄請求正文。
public class LoggingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
try {
chain.doFilter(wrappedRequest, res);
} finally {
logRequestBody(wrappedRequest);
}
}
private static void logRequestBody(ContentCachingRequestWrapper request) {
byte[] buf = request.getContentAsByteArray();
if (buf.length > 0) {
try {
String requestBody = new String(buf, 0, buf.length, request.getCharacterEncoding());
System.out.println(requestBody);
} catch (Exception e) {
System.out.println("error in reading request body");
}
}
}
}
這里要注意的主要事情是您必須在過濾器鏈中傳遞ContentCachingRequestWrapper
對象,否則您將無法在其中獲取請求內容。
在上面的例子中,如果你使用chain.doFilter(req, res)
或chain.doFilter(request, res)
那么你將不會在wrappedRequest
對象中得到請求體。
要記錄 HTTP 請求和響應,您可以使用RequestBodyAdviceAdapter
和ResponseBodyAdvice
。 在這里,它以我的方式使用。
CustomRequestBodyAdviceAdapter.java
@ControllerAdvice
public class CustomRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {
@Autowired
HttpServletRequest httpServletRequest;
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
// here you can full log httpServletRequest and body.
return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
}
CustomResponseBodyAdviceAdapter.java
@ControllerAdvice
public class CustomResponseBodyAdviceAdapter implements ResponseBodyAdvice<Object> {
@Autowired
private LoggingService loggingService;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (serverHttpRequest instanceof ServletServerHttpRequest && serverHttpResponse instanceof ServletServerHttpResponse) {
// here you can full log httpServletRequest and body.
}
return o;
}
}
以上AdviceAdapter
無法處理GET
請求。 因此,您可以使用HandlerInterceptor
。
自定義WebConfigurerAdapter.java
@Component
public class CustomWebConfigurerAdapter implements WebMvcConfigurer {
@Autowired
private CustomLogInterceptor httpServiceInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(httpServiceInterceptor);
}
}
自定義日志攔截器.java
@Component
public class CustomLogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (DispatcherType.REQUEST.name().equals(request.getDispatcherType().name()) && request.getMethod().equals(HttpMethod.GET.name())) {
// here you can full log httpServletRequest and body for GET Request.
}
return true;
}
}
在這里你可以在我的 git 中引用完整的源代碼。
springboot-http-request-response-logging-with-json-logger
+Feature => 它已經與ELK(Elasticsearch、Logstash、Kibana)集成
您可以使用RequestBodyAdviceAdapter獲取 POST/PUT 請求的請求正文數據。 您可以使用HandlerInterceptorAdapter進行 GET 調用。 這是一個工作示例 - https://frandorado.github.io/spring/2018/11/15/log-request-response-with-body-spring.html
@ControllerAdvice
public class CustomRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter
{
@Autowired
HttpServletRequest httpServletRequest;
private static final Log LOGGER = LogFactory.getLog(CustomRequestBodyAdviceAdapter.class);
private static final Charset DEFAULT_CHARSET = ISO_8859_1;
@Override
public boolean supports(MethodParameter methodParameter, Type type,
Class<? extends HttpMessageConverter<?>> aClass)
{
return true;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType)
{
Instant startTime = Instant.now();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("REQUEST call Starts :: Start Time : %s ").append(startTime);
try
{
logRequest(httpServletRequest, body);
}
catch (IOException e)
{
LOGGER.info("Exception getting the Request Body into the Log: {}" + e.getMessage());
}
public void logRequest(HttpServletRequest httpServletRequest, Object body) throws IOException
{
StringBuilder stringBuilder = new StringBuilder();
Map<String, String> parameters = buildParametersMap(httpServletRequest);
stringBuilder.append("REQUEST ");
stringBuilder.append("method=[").append(httpServletRequest.getMethod()).append("] ");
stringBuilder.append("path=[").append(httpServletRequest.getRequestURI()).append("] ");
stringBuilder.append("headers=[").append(buildHeadersMap(httpServletRequest)).append("] ");
if (!parameters.isEmpty())
{
stringBuilder.append("parameters=[").append(parameters).append("] ");
}
if (body != null)
{
stringBuilder.append("body=[" + body + "]");
}
ObjectMapper objectMapper = new ObjectMapper();
String jsonInString = null;
try
{
jsonInString = objectMapper.writer().writeValueAsString(body);
}
catch (JsonProcessingException e)
{
throw new RestApiException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
}
stringBuilder.append("REQUEST Body = [").append(jsonInString).append("] ");
LOGGER.info("BODY DATA >>>> " + jsonInString);
LOGGER.info("Body - : {}" + stringBuilder);
}
private Map<String, String> buildParametersMap(HttpServletRequest httpServletRequest)
{
Map<String, String> resultMap = new HashMap<>();
Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
while (parameterNames.hasMoreElements())
{
String key = parameterNames.nextElement();
String value = httpServletRequest.getParameter(key);
resultMap.put(key, value);
}
return resultMap;
}
private Map<String, String> buildHeadersMap(HttpServletRequest request)
{
Map<String, String> map = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements())
{
String key = headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
}
我在這里使用了 ObjectMapper,因為我需要將主體響應作為原始 JSON 對象,但是afterBodyRead()在主體轉換為 Java 對象后被調用。
我發現這解決了我復制 application/json 內容類型的請求緩沖區的問題。 它還展示了如何擴展包裝器,正如對 Harshit 解決方案的評論中提到的那樣。
重要的是您需要一個過濾器來將新請求傳遞給服務器。
@Component
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
if (Arrays.asList("POST", "PUT").contains(httpRequest.getMethod())) {
CustomHttpRequestWrapper requestWrapper = new CustomHttpRequestWrapper(httpRequest);
requestWrapper.setAttribute("input", requestWrapper.getBodyInStringFormat());
filterChain.doFilter(requestWrapper, servletResponse);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
記錄器需要一個自定義包裝器,而 spring 啟動提供的一個似乎不足以用於 application/json 類型的消息。
public class CustomHttpRequestWrapper extends HttpServletRequestWrapper {
public String getBodyInStringFormat() {
return bodyInStringFormat;
}
private final String bodyInStringFormat;
public CustomHttpRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
bodyInStringFormat = readInputStreamInStringFormat(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
}
private String readInputStreamInStringFormat(InputStream stream, Charset charset) throws IOException {
return getString(stream, charset);
}
static String getString(InputStream stream, Charset charset) throws IOException {
final int MAX_BODY_SIZE = 1024;
final StringBuilder bodyStringBuilder = new StringBuilder();
if (!stream.markSupported()) {
stream = new BufferedInputStream(stream);
}
stream.mark(MAX_BODY_SIZE + 1);
final byte[] entity = new byte[MAX_BODY_SIZE + 1];
final int bytesRead = stream.read(entity);
if (bytesRead != -1) {
bodyStringBuilder.append(new String(entity, 0, Math.min(bytesRead, MAX_BODY_SIZE), charset));
if (bytesRead > MAX_BODY_SIZE) {
bodyStringBuilder.append("...");
}
}
stream.reset();
return bodyStringBuilder.toString();
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream () {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bodyInStringFormat.getBytes());
return new ServletInputStream() {
private boolean finished = false;
@Override
public boolean isFinished() {
return finished;
}
@Override
public int available() {
return byteArrayInputStream.available();
}
@Override
public void close() throws IOException {
super.close();
byteArrayInputStream.close();
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
public int read () {
int data = byteArrayInputStream.read();
if (data == -1) {
finished = true;
}
return data;
}
};
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.