从 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 并用于端点