【问题标题】:RestTemplate response.getBody throws exception on 4** and 5** errors for put and post request but works fine for get requestsRestTemplate response.getBody 对 put 和 post 请求的 4** 和 5** 错误抛出异常,但对 get 请求工作正常
【发布时间】:2018-05-05 21:54:56
【问题描述】:

我正在尝试拦截并记录所有请求响应。为了提出请求,我使用RestTemplate.exchange()。

当我发出GET 请求并收到4** 错误时,我可以调用ClientHttpResponse.getBody() 并可以访问响应正文,但对于PUT 和POST 请求ClientHttpResponse.getBody() 方法会引发异常。

这可能是什么原因造成的,我怎样才能获得POST 和PUT 请求的响应正文?

这是我提出请求的地方:

apiResponse = restTemplate.exchange(url, vCloudRequest.getHttpMethod(), entity, responseType);

这是获取异常的拦截器部分:

@Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        ClientHttpResponse response = execution.execute(request, body);

        String requestString = new String(body);

        String responseString = new 
// Below line throws exception
String(ByteStreams.toByteArray(response.getBody()), Charset.forName("UTF-8"));

这是堆栈。

Caused by: java.io.IOException: Server returned HTTP response code: 403 for URL: https://176.235.57.11/api/admin/org/bd154aaf-2e7c-446d-91be-f0a45138476b/users
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1876)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
    at org.springframework.http.client.SimpleClientHttpResponse.getBody(SimpleClientHttpResponse.java:85)
    at org.springframework.http.client.BufferingClientHttpResponseWrapper.getBody(BufferingClientHttpResponseWrapper.java:69)
    at roma.api_utils.model.Interceptors.RequestLoggingInterceptor.intercept(RequestLoggingInterceptor.java:39)
    at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:86)
    at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:70)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:652)

更新:

当我在调用response.getBody() 之前调用response.getStatusCode() 时,它不会抛出IOException。

【问题讨论】:

  • 您能否提供一些有关异常的详细信息?
  • @qiubix 我添加了堆栈跟踪。
  • 可以添加客户端和目标代码吗?
  • 添加客户端和目标代码将有助于了解客户端如何进行调用和异常处理,以及目标如何接收POST/PUT 请求
  • @neo 我认为这个技巧不应该被接受为解决方案。我不知道这个异常背后的原因,我也不知道这个技巧为什么有效。我的问题是要知道这种行为的原因并对代码有信心。

标签: java spring http resttemplate


【解决方案1】:

基础知识:

HttpURLConnection 有两个相似的字段,errorStream 和 inputStream。当我们调用它的getInputSteam 方法时,它会检查响应是否有错误代码。如果是这样,它会抛出一个IOException 并记录它——这就是你得到异常的原因。此外,它还将inputStream中的内容复制到errorStream,因此我们可以通过调用它的getErrorStream方法来获取它的响应体。这正是 SimpleClientHttpResponse 使用其 getBody 方法所做的:

    @Override
    public InputStream getBody() throws IOException {
        InputStream errorStream = this.connection.getErrorStream();
        this.responseStream = 
(errorStream != null ? errorStream : this.connection.getInputStream());
        return this.responseStream;
    }

它首先检查errorStream 是否不为空。如果为真,则返回它。如果为 false,它会调用 connection.getInputStream() 并返回它。

现在是答案

  1. 为什么在调用response.getStatusCode() 之后调用response.getBody() 不会抛出IOException?这是因为getStatusCode 在内部调用getInputStream。因此,当调用getBody 时,errorStream 不会为空。
  2. 为什么http方法为GET时不抛出异常? 见方法org.springframework.http.client.SimpleBufferingClientHttpRequest#executeInternal。

。

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) 
throws IOException {
    addHeaders(this.connection, headers);
    // JDK <1.8 doesn't support getOutputStream with HTTP DELETE
    if (HttpMethod.DELETE == getMethod() && bufferedOutput.length == 0) {
        this.connection.setDoOutput(false);
    }
    if (this.connection.getDoOutput() && this.outputStreaming) {
        this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
    }
    this.connection.connect();
    if (this.connection.getDoOutput()) {
        FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
    }
    else {
        // Immediately trigger the request in a no-output scenario as well
        this.connection.getResponseCode();
    }
    return new SimpleClientHttpResponse(this.connection);
}

当http方法为GET时,它急切地执行this.connection.getResponseCode();。

【讨论】:

  • 感谢您的详细回答。所以允许在 3 小时内给予赏金,所以我现在不能给予。
  • @SalihErikci 我的荣幸
【解决方案2】:

我有记录每个请求和响应的类似要求。我写了一个过滤器,并钩到过滤器链中。

代码如下所示:

public class CustomRequestFilter implements Filter {


  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    //No custom initialisation required
  }

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
      FilterChain filterChain) throws IOException, ServletException {
    try {

      HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
      HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;

        Map<String, String> requestMap = this
            .getTypesafeRequestMap(httpServletRequest);
        BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(
            httpServletRequest);
        BufferedResponseWrapper bufferedResponse = new BufferedResponseWrapper(
            httpServletResponse);

        final StringBuilder logMessage = new StringBuilder(
            "REST Request - ").append("[HTTP METHOD:")
            .append(httpServletRequest.getMethod())
            .append("] [PATH INFO:")
            .append(httpServletRequest.getServletPath())
            .append("] [REQUEST PARAMETERS:").append(requestMap)
            .append("] [REQUEST BODY:")
            .append(bufferedRequest.getRequestBody())
            .append("] [REMOTE ADDRESS:")
            .append(httpServletRequest.getRemoteAddr()).append("]");
        log.info("=======================REQUEST PAYLOAD=================================");
        log.info(bufferedRequest.getRequestBody());
        log.info("========================================================");
        filterChain.doFilter(bufferedRequest, bufferedResponse);
        logMessage.append(" [RESPONSE:")
            .append(bufferedResponse.getContent()).append("]");
        log.info("=======================REST RESPONSE=================================");
        log.info(bufferedResponse.getContent());
        log.info("========================================================");
             } catch (Exception a) {
      log.error("Error while filtering ", a);
    }
  }

  private Map<String, String> getTypesafeRequestMap(HttpServletRequest request) {
    Map<String, String> typesafeRequestMap = new HashMap<>();
    Enumeration<?> requestParamNames = request.getParameterNames();
    while (requestParamNames.hasMoreElements()) {
      String requestParamName = (String) requestParamNames.nextElement();
      String requestParamValue;
      if ("password".equalsIgnoreCase(requestParamName)) {
        requestParamValue = "********";
      } else {
        requestParamValue = request.getParameter(requestParamName);
      }
      typesafeRequestMap.put(requestParamName, requestParamValue);
    }
    return typesafeRequestMap;
  }

  @Override
  public void destroy() {
    //not yet implemented
  }

  private static final class BufferedRequestWrapper extends
      HttpServletRequestWrapper {

    private ByteArrayInputStream bais = null;
    private ByteArrayOutputStream baos = null;
    private BufferedServletInputStream bsis = null;
    private byte[] buffer = null;

    public BufferedRequestWrapper(HttpServletRequest req)
        throws IOException {
      super(req);
      // Read InputStream and store its content in a buffer.
      InputStream is = req.getInputStream();
      this.baos = new ByteArrayOutputStream();
      byte[] buf = new byte[1024];
      int read;
      while ((read = is.read(buf)) > 0) {
        this.baos.write(buf, 0, read);
      }
      this.buffer = this.baos.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() {
      this.bais = new ByteArrayInputStream(this.buffer);
      this.bsis = new BufferedServletInputStream(this.bais);
      return this.bsis;
    }

    String getRequestBody() throws IOException {
      BufferedReader reader = new BufferedReader(new InputStreamReader(
          this.getInputStream()));
      String line;
      StringBuilder inputBuffer = new StringBuilder();
      do {
        line = reader.readLine();
        if (null != line) {
          inputBuffer.append(line.trim());
        }
      } while (line != null);
      reader.close();
      return inputBuffer.toString().trim();
    }

  }

  private static final class BufferedServletInputStream extends
      ServletInputStream {

    private ByteArrayInputStream bais;

    public BufferedServletInputStream(ByteArrayInputStream bais) {
      this.bais = bais;
    }

    @Override
    public int available() {
      return this.bais.available();
    }

    @Override
    public int read() {
      return this.bais.read();
    }

    @Override
    public int read(byte[] buf, int off, int len) {
      return this.bais.read(buf, off, len);
    }

    @Override
    public boolean isFinished() {
      return false;
    }

    @Override
    public boolean isReady() {
      return true;
    }

    @Override
    public void setReadListener(ReadListener readListener) {
        //No specific readListener changes required
    }
  }

  public class TeeServletOutputStream extends ServletOutputStream {

    private final TeeOutputStream targetStream;

    public TeeServletOutputStream(OutputStream one, OutputStream two) {
      targetStream = new TeeOutputStream(one, two);
    }

    @Override
    public void write(int arg0) throws IOException {
      this.targetStream.write(arg0);
    }

    @Override
    public void flush() throws IOException {
      super.flush();
      this.targetStream.flush();
    }

    @Override
    public void close() throws IOException {
      super.close();
      this.targetStream.close();
    }

    @Override
    public boolean isReady() {
      return false;
    }

    @Override
    public void setWriteListener(WriteListener writeListener) {
      //not yet implemented
    }
  }

  public class BufferedResponseWrapper implements HttpServletResponse {

    HttpServletResponse original;
    TeeServletOutputStream tee;
    ByteArrayOutputStream bos;

    public BufferedResponseWrapper(HttpServletResponse response) {
      original = response;
    }

    public String getContent() {
      return bos.toString();
    }

    @Override
    public PrintWriter getWriter() throws IOException {
      return original.getWriter();
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
      if (tee == null) {
        bos = new ByteArrayOutputStream();
        tee = new TeeServletOutputStream(original.getOutputStream(),
            bos);
      }
      return tee;

    }

    @Override
    public String getCharacterEncoding() {
      return original.getCharacterEncoding();
    }

    @Override
    public String getContentType() {
      return original.getContentType();
    }

    @Override
    public void setCharacterEncoding(String charset) {
      original.setCharacterEncoding(charset);
    }

    @Override
    public void setContentLength(int len) {
      original.setContentLength(len);
    }

    @Override
    public void setContentLengthLong(long l) {
      original.setContentLengthLong(l);
    }

    @Override
    public void setContentType(String type) {
      original.setContentType(type);
    }

    @Override
    public void setBufferSize(int size) {
      original.setBufferSize(size);
    }

    @Override
    public int getBufferSize() {
      return original.getBufferSize();
    }

    @Override
    public void flushBuffer() throws IOException {
      tee.flush();
    }

    @Override
    public void resetBuffer() {
      original.resetBuffer();
    }

    @Override
    public boolean isCommitted() {
      return original.isCommitted();
    }

    @Override
    public void reset() {
      original.reset();
    }

    @Override
    public void setLocale(Locale loc) {
      original.setLocale(loc);
    }

    @Override
    public Locale getLocale() {
      return original.getLocale();
    }

    @Override
    public void addCookie(Cookie cookie) {
      original.addCookie(cookie);
    }

    @Override
    public boolean containsHeader(String name) {
      return original.containsHeader(name);
    }

    @Override
    public String encodeURL(String url) {
      return original.encodeURL(url);
    }

    @Override
    public String encodeRedirectURL(String url) {
      return original.encodeRedirectURL(url);
    }

    @SuppressWarnings("deprecation")
    @Override
    public String encodeUrl(String url) {
      return original.encodeUrl(url);
    }

    @SuppressWarnings("deprecation")
    @Override
    public String encodeRedirectUrl(String url) {
      return original.encodeRedirectUrl(url);
    }

    @Override
    public void sendError(int sc, String msg) throws IOException {
      original.sendError(sc, msg);
    }

    @Override
    public void sendError(int sc) throws IOException {
      original.sendError(sc);
    }

    @Override
    public void sendRedirect(String location) throws IOException {
      original.sendRedirect(location);
    }

    @Override
    public void setDateHeader(String name, long date) {
      original.setDateHeader(name, date);
    }

    @Override
    public void addDateHeader(String name, long date) {
      original.addDateHeader(name, date);
    }

    @Override
    public void setHeader(String name, String value) {
      original.setHeader(name, value);
    }

    @Override
    public void addHeader(String name, String value) {
      original.addHeader(name, value);
    }

    @Override
    public void setIntHeader(String name, int value) {
      original.setIntHeader(name, value);
    }

    @Override
    public void addIntHeader(String name, int value) {
      original.addIntHeader(name, value);
    }

    @Override
    public void setStatus(int sc) {
      original.setStatus(sc);
    }

    @SuppressWarnings("deprecation")
    @Override
    public void setStatus(int sc, String sm) {
      original.setStatus(sc, sm);
    }

    @Override
    public String getHeader(String arg0) {
      return original.getHeader(arg0);
    }

    @Override
    public Collection<String> getHeaderNames() {
      return original.getHeaderNames();
    }

    @Override
    public Collection<String> getHeaders(String arg0) {
      return original.getHeaders(arg0);
    }

    @Override
    public int getStatus() {
      return original.getStatus();
    }

  }
}

【讨论】:

  • 感谢您的回答,但我的问题不是怎么做,而是为什么我得到了特定的异常。
【解决方案3】:

对于PUT 和POST,这取决于您的目标资源。如果您的目标资源在 PUT 或 POST 请求之后没有在响应正文中添加任何内容,则出现异常是正常的。通常,您知道使用PUT 或POST 发送的资源,因此您只需检查响应的状态即可了解您的资源是否已创建或修改。您无需再次检查响应正文。

【讨论】:

【解决方案4】:

您可以使用以下内容访问拦截器中的响应正文。我做了一个快速的单元测试,以确认它即使在带有 403 响应的 POST 上也能正常工作。

但是要小心,getBody 返回一个 InputStream。这意味着您只能阅读一次。除非您提供带有新正文的新响应,否则您将无法在拦截器之外再次读取相同的流。

...
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    final ClientHttpResponse response = execution.execute(request, body);
    final InputStream body = response.getBody();
    return response;
}
...

【讨论】:

  • 问题是我有一个记录请求-响应对的拦截器。这个拦截器需要获取响应体。
  • 发生403错误时是否需要处理响应体?如果没有,您可以先检查状态码,如果它是 2xx 状态,则仅处理。
  • 即使发生错误,我也需要记录响应正文。
  • 更新以更好地适应拦截器的问题。
  • response.getBody()在请求方式为POST或PUT时抛出异常
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 2021-11-19
  • 1970-01-01
  • 2016-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多