从 HttpServletRequest 获取 XML 并用于端点

Posted

技术标签:

【中文标题】从 HttpServletRequest 获取 XML 并用于端点【英文标题】:Get XML from HttpServletRequest and use into endpoint 【发布时间】:2019-04-26 22:44:15 【问题描述】:

我想从请求和响应中获取 XML 数据并将其用于 Rest 控制器。我试过这个:

@RestController()
    public class HomeController 

        @PostMapping(value = "/v1")
      public Response handleMessage(@RequestBody Transaction transaction, HttpServletRequest request, HttpServletResponse response) throws Exception 

           HttpServletRequest request, HttpServletResponse response

            System.out.println("!!!!!!! InputStream");
            System.out.println(request.getInputStream());
            System.out.println(response.getOutputStream());

            InputStream in = request.getInputStream();
            String readLine;
            BufferedReader br = new BufferedReader(new InputStreamReader(in));

            while (((readLine = br.readLine()) != null)) 
                System.out.println(readLine);    
            
          
    

但我得到java.io.IOException: UT010029: Stream is closed

将内容放入 String 变量的正确方法是什么?

编辑:我也尝试了过滤器的解决方案,但我不知道如何将请求有效负载用于休息控制器:

读取请求负载:

@Component
public class HttpLoggingFilter implements Filter 

    private static final Logger logger = LoggerFactory.getLogger(HttpLoggingFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException 
    

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException 

        ResettableStreamHttpServletRequest wrappedRequest = new ResettableStreamHttpServletRequest((HttpServletRequest) request);
        wrappedRequest.getInputStream().read();
        String body = IOUtils.toString(wrappedRequest.getReader());
        System.out.println("!!!!!!!!!!!!!!!!!! " + body);
        wrappedRequest.resetInputStream();


        chain.doFilter(request, response);
    

    public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper 
        private byte[] rawData;
        private HttpServletRequest request;
        private ResettableServletInputStream servletStream;

        ResettableStreamHttpServletRequest(HttpServletRequest request) 
            super(request);
            this.request = request;
            this.servletStream = new ResettableServletInputStream();
        

        void resetInputStream() 
            servletStream.stream = new ByteArrayInputStream(rawData);
        

        @Override
        public ServletInputStream getInputStream() throws IOException 
            if (rawData == null) 
                rawData = IOUtils.toByteArray(this.request.getInputStream());
                servletStream.stream = new ByteArrayInputStream(rawData);
            
            return servletStream;
        

        @Override
        public BufferedReader getReader() throws IOException 
            if (rawData == null) 
                rawData = IOUtils.toByteArray(this.request.getInputStream());
                servletStream.stream = new ByteArrayInputStream(rawData);
            
            String encoding = getCharacterEncoding();
            if (encoding != null) 
                return new BufferedReader(new InputStreamReader(servletStream, encoding));
             else 
                return new BufferedReader(new InputStreamReader(servletStream));
            
        

        private class ResettableServletInputStream extends ServletInputStream 
            private InputStream stream;

            @Override
            public int read() throws IOException 
                return stream.read();
            

            @Override
            public boolean isFinished() 
                // TODO Auto-generated method stub
                return false;
            

            @Override
            public boolean isReady() 
                // TODO Auto-generated method stub
                return false;
            

            @Override
            public void setReadListener(ReadListener readListener) 
                // TODO Auto-generated method stub

            
        
       

休息端点:

@RestController()
public class HomeController 

    @PostMapping(value = "/v1")
  public Response handleMessage(@RequestBody Transaction transaction, HttpServletRequest request, org.zalando.logbook.HttpRequest requestv, HttpServletResponse response) throws Exception 

       // Get here request and response and log it into DB
      

如何将HttpLoggingFilter 调用到Java 方法handleMessage 中并将请求作为正文字符串获取?也许我可以让它服务并注入它?您能给我一些建议吗?如何评估代码?

【问题讨论】:

你能发布完整的异常堆栈吗? 你想在哪里做这个?请添加更多上下文。您尝试执行此操作的时间点很重要,因为流可能已经被其他进程读取。如果有更多的上下文,也许我们可以提供一些替代方案。哦,你使用的是什么框架。请在您的帖子中添加适当的标签。 我在这里添加了描述:***.com/questions/53450695/get-xml-in-plain-text 你已经有一个 comment 说明你需要做什么:你需要在 Filter 中执行此操作并包装传入的请求。我> hm.... HttpLoggingFilter 是过滤器吗? 【参考方案1】:

这里有很多类可以做到这一点。这是一个 OncePerRequestFilter 实现,请在此处查看 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html。基本上问题是在链式过滤器中,请求流和响应流只能读取一次。因此,需要将这 2 个流包装在可以多次读取的内容中。

在前 2 行中,我将请求和响应包装在 requestToUse 和 responseToUse 中。 ResettableStreamHttpServletRequest 和 ResettableStreamHttpServletResponse 是包装类,它们将原始字符串主体保留在其中,并且每次需要流时,它们都会返回一个新流。然后,您将忘记请求和响应,并开始使用 requestToUse 和 responseToUse。

我从我做的一个旧项目中得到这个。实际上还有更多的类,但我为您提取了主要部分。这可能无法立即编译。试一试,告诉我,我会帮助你完成它。

    public class RequestResponseLoggingFilter extends OncePerRequestFilter 

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException 
        //here you wrap the request and response into some resetable istream class
        HttpServletRequest requestToUse = new ResettableStreamHttpServletRequest(request);
        HttpServletResponse responseToUse = new ResettableStreamHttpServletResponse(response);

        //you read the request to log it
        byte[] payload = IOUtils.toByteArray(requestToUse.getReader(), requestToUse.getCharacterEncoding());      
        String body =  new String(payload, requestToUse.getCharacterEncoding());

        //here you log the body request
        log.(body);

        //let the chain continue
        filterChain.doFilter(requestToUse, responseToUse);

        // Here we log the response
        String response =  new String(responseToUse.toString().getBytes(), responseToUse.getCharacterEncoding());

        //since you can read the stream just once, you will need it again for chain to be able to continue, so you reset it
        ResettableStreamHttpServletResponse responseWrapper = WebUtils.getNativeResponse(responseToUse, ResettableStreamHttpServletResponse.class);
        if (responseWrapper != null) 
            responseWrapper.copyBodyToResponse(true);
        
    



    public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper 

    private byte[] rawData;
    private ResettableServletInputStream servletStream;

    public ResettableStreamHttpServletRequest(HttpServletRequest request) throws IOException 
        super(request);
        rawData = IOUtils.toByteArray(request.getInputStream());
        servletStream = new ResettableServletInputStream();
        servletStream.setStream(new ByteArrayInputStream(rawData));
    

    @Override
    public ServletInputStream getInputStream() throws IOException 
        servletStream.setStream(new ByteArrayInputStream(rawData));
        return servletStream;
    

    @Override
    public BufferedReader getReader() throws IOException 
        servletStream.setStream(new ByteArrayInputStream(rawData));
        return new BufferedReader(new InputStreamReader(servletStream));
    



    public class ResettableStreamHttpServletResponse extends HttpServletResponseWrapper 

    private ByteArrayServletOutputStream byteArrayServletOutputStream = new ByteArrayServletOutputStream();

    public ResettableStreamHttpServletResponse(HttpServletResponse response) throws IOException 
        super(response);
    

    /**
     * Copy the cached body content to the response.
     *
     * @param complete whether to set a corresponding content length for the complete cached body content
     * @since 4.2
     */
    public void copyBodyToResponse(boolean complete) throws IOException 
        byte[] array = byteArrayServletOutputStream.toByteArray();
        if (array.length > 0) 
            HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
            if (complete && !rawResponse.isCommitted()) 
                rawResponse.setContentLength(array.length);
            
            rawResponse.getOutputStream().write(byteArrayServletOutputStream.toByteArray());
            if (complete) 
                super.flushBuffer();
            
        
    

    /**
     * The default behavior of this method is to return getOutputStream() on the wrapped response object.
     */
    @Override
    public ServletOutputStream getOutputStream() throws IOException 
        return byteArrayServletOutputStream;
    

    /**
     * The default behavior of this method is to return getOutputStream() on the wrapped response object.
     */
    @Override
    public String toString() 
        String response = new String(byteArrayServletOutputStream.toByteArray());
        return response;
    


【讨论】:

一个问题不清楚 - 我如何将此代码调用到 Java 方法 handleMessage 中并将请求作为正文字符串获取?您能给我一些建议,我可以如何评估代码吗? 您不会从您的 hadleMessage 方法中调用此逻辑。这是一个 Servlet 过滤器。在这里你可以了解他们javaee.github.io/servlet-spec/downloads/servlet-4.0/…。基本上,在您的 Spring Boot 应用程序中,在任何 @Configuration 类中,您都可以像这样定义一个过滤器 bean:pastebin.com/BeH58kDR。 Spring Boot 将注册您的过滤器并开始拦截您的所有请求/响应。【参考方案2】:

您不需要在这里做任何特别的事情,Spring 框架会为您完成。 您只需要:

    创建一个代表您的 XML 数据的 Pojo 或 Bean。

    向 Gradle/Maven 添加 xml 数据格式依赖项,这会将请求 xml 绑定到您的 pojo。

     compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.9.9'
    

    告诉您的请求处理程序接受这样的 XML:

    @RequestMapping(value = "/xmlexample", method = RequestMethod.POST,consumes = "application/xml;charset=UTF-8") 
    public final boolean transactionHandler(@Valid @RequestBody Transaction transaction) 
        log.debug("Received transaction request with data ", transaction);
        return true;
    
    

瞧,您的事务 bean 将填充您的 XML 数据。

【讨论】:

这个解决方案的问题是它不能扩展。您的端点编号可能会随着时间的推移而变得疯狂。我曾为一个拥有 20 多个应用程序的组织工作,每个应用程序可能有 20-50 个端点,具体取决于应用程序的类型。使用此解决方案,如果您的 pojo 得到更新,您将需要更新每个地方 @Perimosh 好吧,像往常一样,这真的取决于。如果您正在使用 springboot(读取微服务),您可能不会有大量的端点,因此不需要到处更新。即使你需要,它毕竟不是好的设计。此外,归根结底,您必须将 xml 映射到 java 类才能从中获得任何有用的东西,除非您只是将其转储到水槽中。无论如何,这取决于 OP 的要求是什么。 我完全不同意。我们从几个端点开始。然后一切都开始快速成长。无论您运行的是 monolinte 应用程序还是微服务,您都需要以一种优雅、可扩展和可测试的方式来处理这种情况。在代码中丢弃很多“log.info”行,感觉不太好。我的基本原则是,编写代码对扩展开放,对修改关闭。拥有一个过滤器来记录您的所有端点让您的生活变得轻松,并且您不需要触摸每个地方。不过对话很好!谢谢。

以上是关于从 HttpServletRequest 获取 XML 并用于端点的主要内容,如果未能解决你的问题,请参考以下文章

如何从 HttpServletRequest 获取完整的 url? [复制]

从 HttpServletRequest 获取 AsyncContext

从 HttpServletRequest 获取 XML 并用于端点

如何从 HttpServletRequest 获取请求 url [重复]

从HttpServletRequest中获取body

如何从httpservletrequest requset获取requestid