我已经使用Spring RestTemplate有一段时间了,当我试图调试它的请求和响应时,我总是碰壁。我基本上希望看到与打开“verbose”选项时使用curl时相同的东西。例如:

curl -v http://twitter.com/statuses/public_timeline.rss

将显示发送的数据和接收的数据(包括头、cookie等)。

我看了一些相关的帖子,比如: 如何在Spring RestTemplate中记录响应? 但我还没能解决这个问题。

实现这一点的一种方法是实际更改RestTemplate源代码,并在那里添加一些额外的日志记录语句,但我认为这种方法确实是最后的办法。应该有某种方法告诉Spring Web Client/RestTemplate以一种更友好的方式记录所有内容。

我的目标是能够用如下代码做到这一点:

restTemplate.put("http://someurl", objectToPut, urlPathValues);

然后在日志文件或控制台中获得相同类型的调试信息(就像我使用curl获得的一样)。 我相信这对于任何使用Spring RestTemplate并且遇到问题的人来说都是非常有用的。使用curl来调试RestTemplate问题是行不通的(在某些情况下)。


当前回答

---- 2019年7月----

(使用Spring Boot)

让我感到惊讶的是,Spring Boot拥有所有的零配置魔法,却没有提供一种使用RestTemplate检查或记录简单JSON响应体的简单方法。我浏览了这里提供的各种答案和评论,并分享了我自己的(仍然)有效的版本,并且在我看来是一个合理的解决方案,考虑到当前的选项(我使用Spring Boot 2.1.6和Gradle 4.4)

1. 使用Fiddler作为http代理

这实际上是一个相当优雅的解决方案,因为它绕过了创建自己的拦截器或将底层http客户端更改为apache的所有繁琐工作(见下文)。

安装并运行Fiddler

然后

add -DproxySet=true -Dhttp。proxyHost = localhost -Dhttp。proxyPort=8888到你的虚拟机选项

2. 使用Apache HttpClient

将Apache HttpClient添加到Maven或Gradle依赖项中。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.9</version>
</dependency>

使用HttpComponentsClientHttpRequestFactory作为RestTemplate的RequestFactory。最简单的方法是:

RestTemplate = new RestTemplate(); 创建restTemplate。setRequestFactory(新HttpComponentsClientHttpRequestFactory ());

在应用程序中启用DEBUG。属性文件(如果您使用Spring Boot)

logging.level.org.apache.http =调试

如果你正在使用Spring Boot,你需要确保你有一个日志框架的设置,例如通过使用一个Spring - Boot -starter依赖项,其中包括Spring - Boot -starter-logging。

3.使用拦截器

我会让你通读提案、反提案以及其他答案和评论中的陷阱,然后你自己决定是否要走这条路。

4. 无正文记录URL和响应状态

尽管这并不满足记录主体的要求,但这是开始记录REST调用的一种快速而简单的方法。它显示完整的URL和响应状态。

只需将以下行添加到应用程序中。properties文件(假设您正在使用Spring Boot,并且假设您正在使用包含Spring - Boot -starter-logging的Spring Boot启动器依赖项)

logging.level.org.springframework.web.client.RestTemplate =调试

输出看起来像这样:

2019-07-29 11:53:50.265 DEBUG o.s.web.client.RestTemplate : HTTP GET http://www.myrestservice.com/Endpoint?myQueryParam=myValue
2019-07-29 11:53:50.276 DEBUG o.s.web.client.RestTemplate : Accept=[application/json]
2019-07-29 11:53:50.584 DEBUG o.s.web.client.RestTemplate : Response 200 OK
2019-07-29 11:53:50.585 DEBUG o.s.web.client.RestTemplate : Reading to [org.mynamespace.MyJsonModelClass]

其他回答

我的日志记录器配置使用XML

<logger name="org.springframework.web.client.RestTemplate">
    <level value="trace"/>
</logger>

然后你会得到如下内容:

DEBUG org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:92) : Reading [com.test.java.MyClass] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@604525f1]

通过HttpMessageConverterExtractor.java:92,你需要继续调试,在我的情况下,我得到了这个:

genericMessageConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);

这:

outputMessage.getBody().flush();

outputMessage.getBody()包含http(post类型)发送的消息

Related to the response using ClientHttpInterceptor, I found a way of keeping the whole response without Buffering factories. Just store the response body input stream inside byte array using some utils method that will copy that array from body, but important, surround this method with try catch because it will break if response is empty (that is the cause of Resource Access Exception) and in catch just create empty byte array, and than just create anonymous inner class of ClientHttpResponse using that array and other parameters from the original response. Than you can return that new ClientHttpResponse object to the rest template execution chain and you can log response using body byte array that is previously stored. That way you will avoid consuming InputStream in the actual response and you can use Rest Template response as it is. Note, this may be dangerous if your's response is too big

我浏览了所有的答案,如果你需要设置认证类型或连接超时,那么你可以这样做:

SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(Integer.valueOf(YOUR_VALUE));
factory.setReadTimeout(Integer.valueOf(YOUR_VALUE));

RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(factory));
restTemplate.getInterceptors().add(new LoggingRequestInterceptor());
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(USER_NAME,PASSWORD,StandardCharsets.UTF_8));
return restTemplate;

正如@MilacH指出的,在实现中有一个错误。如果返回statusCode > 400,则从拦截器抛出IOException,因为没有调用errorHandler。可以忽略异常,然后在处理程序方法中再次捕获异常。

package net.sprd.fulfillment.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import static java.nio.charset.StandardCharsets.UTF_8;

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    final static Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);

    @SuppressWarnings("HardcodedLineSeparator")
    public static final char LINE_BREAK = '\n';

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        try {
            traceRequest(request, body);
        } catch (Exception e) {
            log.warn("Exception in LoggingRequestInterceptor while tracing request", e);
        }

        ClientHttpResponse response = execution.execute(request, body);

        try {
            traceResponse(response);
        } catch (IOException e) {
            // ignore the exception here, as it will be handled by the error handler of the restTemplate
            log.warn("Exception in LoggingRequestInterceptor", e);
        }
        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) {
        log.info("===========================request begin================================================");
        log.info("URI         : {}", request.getURI());
        log.info("Method      : {}", request.getMethod());
        log.info("Headers     : {}", request.getHeaders());
        log.info("Request body: {}", new String(body, UTF_8));
        log.info("==========================request end================================================");
    }

    private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), UTF_8))) {
            String line = bufferedReader.readLine();
            while (line != null) {
                inputStringBuilder.append(line);
                inputStringBuilder.append(LINE_BREAK);
                line = bufferedReader.readLine();
            }
        }

        log.info("============================response begin==========================================");
        log.info("Status code  : {}", response.getStatusCode());
        log.info("Status text  : {}", response.getStatusText());
        log.info("Headers      : {}", response.getHeaders());
        log.info("Response body: {}", inputStringBuilder);
        log.info("=======================response end=================================================");
    }

}

参考Q/A,通过在HttpInputStream上启用多次读取来记录其余模板的请求和响应

为什么我的自定义ClientHttpRequestInterceptor与空响应