【问题标题】:Retrieve Request Body in Exception Mapper在异常映射器中检索请求正文
【发布时间】:2011-11-11 14:49:30
【问题描述】:

我正在尝试在 JAX-RS ExceptionMapper 中检索请求的正文。到目前为止,这是我的代码:

@Provider @Componenet
public class BaseExceptionMapper implements ExceptionMapper<Exception> {

    @Context private HttpServletRequest request;

    @Override
    public Response toResponse(Exception ex) {

        // Trying to retrieve request body for logging throws an error
        String requestBody = IOUtils.toString(request.getInputStream());

    }

}

所以我的困境是我无法获取用于记录的请求正文,因为 servlet API 不允许您为请求多次调用 request.getInputStream() / request.getReader() (而 JAX-RS 显然是调用它来解析请求)。有谁知道是否有办法做我想做的事?

【问题讨论】:

  • 我猜您需要创建自定义异常,将已解析的实体包含在其中并在您的 ExceptionMapper 中重用该值..

标签: java servlets jersey jax-rs


【解决方案1】:

这个问题有点老了,但答案仍然可以帮助其他人。我的示例也依赖于 Commons-Io。

您可以创建一个 ContainerRequestFilter 并使用 TeeInputStream 代理/复制原始 InputStream:

@Provider
@Priority(Priorities.ENTITY_CODER)
public class CustomRequestWrapperFilter implements ContainerRequestFilter { 

    @Override
    public void filter(ContainerRequestContext requestContext)
            throws IOException {
        ByteArrayOutputStream proxyOutputStream = new ByteArrayOutputStream();
        requestContext.setEntityStream(new TeeInputStream(requestContext.getEntityStream(), proxyOutputStream));
        requestContext.setProperty("ENTITY_STREAM_COPY", proxyOutputStream);
    }

}

并在 ExceptionMapper 中使用 @Inject 和 javax.inject.Provider 来注入 ContainerRequest。

ExceptionMapper 看起来像这样:

@Provider
public class BaseExceptionMapper implements ExceptionMapper<Exception> {

    @Inject
    private javax.inject.Provider<ContainerRequest> containerRequestProvider;

    @Override
    public Response toResponse(Exception exception) {
        ByteArrayOutputStream bos = (ByteArrayOutputStream) containerRequestProvider
                .get().getProperty("ENTITY_STREAM_COPY");
        String requestBody = bos.toString();
        ...
    }

}

当我还使用了@Component 注释时,我的 ExceptionMapper 没有被使用。我认为@Provider 就足够了。

【讨论】:

  • @friedea 这仅返回请求正文,但我需要所有可能的信息来发送包含所有详细信息的邮件,例如:1>请求 URL 2>请求方法 3>请求正文 4>路径参数 5>查询参数6>IP地址等
  • 这不是泄露了OutputStream吗?什么时候关门?
  • setEntityStream docs 说:“JAX-RS 运行时负责关闭输入流”
【解决方案2】:

一种可能的解决方案是使用 servlet 过滤器并包装请求,这允许您拦截对请求输入流的读取调用。示例伪代码(取决于commons-io):

import org.apache.commons.io.output.StringBuilderWriter;
import org.apache.commons.io.input.TeeInputStream;
class MyHttpRequest extends HttpServletRequestWrapper {
    private StringBuilderWriter myString = new StringBuilderWriter();
    private InputStream myIn;
    public MyHttpRequest(HttpServletRequest request) {
        super(request);
        myIn = new TeeInputStream(request.getInputStream(), myString);
    }
    @Override public ServletInputStream getInputStream()
            throws java.io.IOException {
        // this will need an extra wrapper to compile
        return myIn;
    }
    public String getRequestBody() {
        return myString.toString();
    }
}

过滤器:

public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
    MyHttpRequest wrapper = new MyHttpRequest((HttpServletRequest) request);
    chain.doFilter(wrapper, response, chain);
}

映射器:

@Context private HttpServletRequest request;
@Override public Response toResponse(Exception ex) {
    String body = "";
    if (this.request instanceof MyHttpRequest) {
        body = ((MyHttpRequest)request).getRequestBody()
    }
}

您需要ServletInputStream 的包装类,您可以在此处找到示例实现:Modify HttpServletRequest body

【讨论】:

  • 我已经尝试过您的解决方案,但在 toResponse 方法中,request 绝不是 MyHttpRequest 的实例。相反,requestcom.sun.proxy.$Proxy 的一个实例,我找不到将其转换为所需类的方法。有什么想法吗?
  • @FredericoPantuzadeMeira 你确定你的过滤器正在运行吗?代理类通常由框架生成,但您的过滤器应该包装生成的类。您也可以尝试创建一个接口并在您的过滤器中提供一个实现,否则仅引用该接口。另请查看docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html
猜你喜欢
  • 2013-11-27
  • 1970-01-01
  • 2020-08-29
  • 2017-06-24
  • 1970-01-01
  • 2020-01-22
  • 1970-01-01
  • 1970-01-01
  • 2020-12-03
相关资源
最近更新 更多