restTemplate源码解析(五)处理ClientHttpResponse响应对象

所有文章

https://www.cnblogs.com/lay2017/p/11740855.html

正文

上一篇文章中,我们执行了ClientHttpRequest与服务端进行交互。并返回了一个ClientHttpResponse的实例对象。

本文将继续最后一个部分,处理请求后的响应。

同样的,我们再次回顾一下restTemplate核心逻辑代码

protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
        @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {

    ClientHttpResponse response = null;
    try {
        // 生成请求
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {
            // 设置header
            requestCallback.doWithRequest(request);
        }
        // 执行请求,获取响应
        response = request.execute();
        // 处理响应
        handleResponse(url, method, response);
        // 获取响应体对象
        return (responseExtractor != null ? responseExtractor.extractData(response) : null);
    }
    catch (IOException ex) {
        // ... 抛出异常
    }
    finally {
        if (response != null) {
            // 关闭响应流
            response.close();
        }
    }
}

我们的关注重点在于execute之后做了哪些事情

handleResponse

先跟进handleResponse方法

protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
    ResponseErrorHandler errorHandler = getErrorHandler();
    boolean hasError = errorHandler.hasError(response);
    if (logger.isDebugEnabled()) {
        try {
            int code = response.getRawStatusCode();
            HttpStatus status = HttpStatus.resolve(code);
            logger.debug("Response " + (status != null ? status : code));
        }
        catch (IOException ex) {
            // ignore
        }
    }
    if (hasError) {
        errorHandler.handleError(url, method, response);
    }
}

getErrorHandler获取了一个错误处理器,如果Response的状态码是错误的,那么就调用handleError处理错误并抛出异常。

extractData生成响应对象

跟进extractData方法

public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
    if (this.delegate != null) {
        T body = this.delegate.extractData(response);
        return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);
    }
    else {
        return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).build();
    }
}

这里主要是将状态码和相依你个对象包装成一个ResponseEntity,然后返回。

我们看看body的生成。

先看一下delegate这个代理类是怎么来的

@Nullable
private final HttpMessageConverterExtractor<T> delegate;

public ResponseEntityResponseExtractor(@Nullable Type responseType) {
    if (responseType != null && Void.class != responseType) {
        this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
    }
    else {
        this.delegate = null;
    }
}

其实就是把RestTemplate构造方法中添加的HttpMessageConverter给包装了一下

跟进delegate看看它的extractData方法吧

public T extractData(ClientHttpResponse response) throws IOException {
    MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
    if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
        return null;
    }
    MediaType contentType = getContentType(responseWrapper);

    try {
        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
            if (messageConverter instanceof GenericHttpMessageConverter) {
                GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter<?>) messageConverter;
                // 判断是否能够读取
                if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
                    // 读取
                    return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
                }
            }
            if (this.responseClass != null) {
                // 判断是否能够读取
                if (messageConverter.canRead(this.responseClass, contentType)) {
                    // 读取
                    return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
                }
            }
        }
    }
    // ...
}

核心逻辑就是遍历HttpMessageConveter,如果能够读取数据,那么就调用read方法读取数据。

那么,我们看看HttpMessageConverter的直接实现类AbstractHttpMessageConverter吧,阅读一下它的read方法

public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
        throws IOException, HttpMessageNotReadableException {

    return readInternal(clazz, inputMessage);
}

再跟进readInternal方法,readInternal向下有很多实现,如

我们选择一个简单的实现看看,StringHttpMessageConverter类的readInternal方法

@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
    Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
    return StreamUtils.copyToString(inputMessage.getBody(), charset);
}

String的转换非常简单,getBody方法将获取到ClientHttpResponse中的输入流。copyToString将从输入流中读取数据,并返回字符串结果。

打开copyToString看看

public static String copyToString(@Nullable InputStream in, Charset charset) throws IOException {
    if (in == null) {
        return "";
    }

    StringBuilder out = new StringBuilder();
    InputStreamReader reader = new InputStreamReader(in, charset);
    char[] buffer = new char[BUFFER_SIZE];
    int bytesRead = -1;
    while ((bytesRead = reader.read(buffer)) != -1) {
        out.append(buffer, 0, bytesRead);
    }
    return out.toString();
}

到这里,我们就使用HttpMessageConverter把响应实体对象生成了。正如前面说到的,会把状态码和响应实体对象包装成ResponseEntity返回给用户。用户只需要通过get方法就可以获取statusCode或者body了。

关闭输入流

restTemplate还有最后一步操作需要去做,就是在finally块中关闭输入流

finally {
    if (response != null) {
        response.close();
    }
}

ClientHttpResponse的close方法将包含输入流的关闭

@Override
public void close() {
    try {
        if (this.responseStream == null) {
            getBody();
        }
        StreamUtils.drain(this.responseStream);
        this.responseStream.close();
    }
    catch (Exception ex) {
        // ignore
    }
}

总结

到这里,restTemplate的源码解读文章就全部结束了。总的来说是一个基于Http请求响应模型的,采用了restful风格的实现方式。

原文地址:https://www.cnblogs.com/lay2017/p/11743088.html

时间: 2024-11-03 22:22:25

restTemplate源码解析(五)处理ClientHttpResponse响应对象的相关文章

Spring 源码解析之DispatcherServlet源码解析(五)

Spring 源码解析之DispatcherServlet源码解析(五) 前言 本文需要有前四篇文章的基础,才能够清晰易懂,有兴趣可以先看看详细的流程,这篇文章可以说是第一篇文章,也可以说是前四篇文章的的汇总,Spring的整个请求流程都是围绕着DispatcherServlet进行的 类结构图 根据类的结构来说DispatcherServlet本身也是继承了HttpServlet的,所有的请求都是根据这一个Servlet来进行转发的,同时解释了为什么需要在web.xml进行如下配置,因为Spr

Tomcat请求处理过程(Tomcat源码解析五)

前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求->建立Socket连接->通过Socket读取数据->根据http协议解析数据->调用后台服务完成响应,详细的流程图如上图所示,等读者读完本篇,应该就清楚了上图所表达的意思.Tomcat既是一个HttpServer也是一个Servlet 容器,那么这里必然也涉及到如上过程,首先根据HTTP协议

iOS即时通讯之CocoaAsyncSocket源码解析五

接上篇:iOS即时通讯之CocoaAsyncSocket源码解析四         原文 正文待补...

Curator源码解析(五)Curator的连接和重试机制

转载请注明出处: jiq?钦's technical Blog 本文将主要关注Curator是如何处理连接丢失和会话终止这两个关键问题的. 1.   连接丢失的处理 Curator中利用类ConnectionState来管理客户端到ZooKeeper集群的连接状态,其中用到原子布尔型变量来标识当前连接是否已经建立: private finalAtomicBoolean isConnected= newAtomicBoolean(false); 在事件处理函数中(ConnectionState实现

jquery源码解析:jQuery静态属性对象support详解

jQuery.support是用功能检测的方法来检测浏览器是否支持某些功能.针对jQuery内部使用. 我们先来看一些源码: jQuery.support = (function( support ) { ...... return support;})( {} ); jQuery.support其实就是一个json对象.在火狐浏览器下,打印出support对象: 接下来,我们来看它的源码 jQuery.support = (function( support ) { var input = d

AFNetworking (3.1.0) 源码解析 &lt;五&gt;

这次主要开始讲解一下文件夹Serialization下的类AFURLRequestSerialization. AFURLRequestSerialization类遵守`AFURLRequestSerialization`和`AFURLResponseSerialization`协议,提供一个查询字符串/表单编码的参数序列化和默认请求头的具体的基本的实现,以及响应状态代码和内容类型验证.也就是对发出的请求进行一些处理. 处理HTTP的任何请求或响应序列化被鼓励归入“AFHTTPRequestSe

restTemplate源码解析(四)执行ClientHttpRequest请求对象

所有文章 https://www.cnblogs.com/lay2017/p/11740855.html 正文 上一篇文章中,我们创建了一个ClientHttpRequest的实例.本文将继续阅读ClientHttpRequest的执行逻辑. 再次回顾一下restTemplate核心逻辑的代码 protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCall

f2fs源码解析(五) node管理结构梳理

node是f2fs重要的管理结构, 它非常重要! 系统挂载完毕后, 会有一个f2fs_nm_info结构的node管理器来管理node的分配. f2fs_nm_info中最让人疑惑的是几颗基数树: 490 struct f2fs_nm_info { 491 block_t nat_blkaddr; /* base disk address of NAT */ 492 nid_t max_nid; /* maximum possible node ids */ 494 nid_t next_sca

Vue-Router 源码解析(五) router-link组件的用法及原理

该组件支持用户在具有路由功能的应用中(点击)导航,默认渲染成带有正确链接的<a>标签,可以通过tag属性生成别的标签. 它本质上是通过在生成的标签上绑定了click事件,然后执行对应的VueRouter实例的push()实现的,对于router-link组件来说,可以传入以下props: to                                    表示目标路由的链接,当被点击后,内部会立刻把to的值传到router.push(),所以这个值可以是一个字符串或者是描述目标位置的对