Spring RestTemplate请求过程

news2025/1/13 7:47:37

文章目录

  • 前言
  • 1. RestTemplate请求整体过程
  • 2. httpRequest 创建
  • 3. doWithRequest
  • 4. execute
  • 5. http响应解析


前言

目前Spring RestTemplate是常用的http请求工具类,本文简单Spring RestTemplate的请求过程。

1. RestTemplate请求整体过程

接下来以ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
ParameterizedTypeReference responseType, Object… uriVariables)方法为起点,揭开http请求过程。

public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
		ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {

	Type type = responseType.getType();
	RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
	ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
	return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}

http的整体流程如下所示,首先创建httpRequest,其次处理httpRequest,最重要就是执行http请求,最后就是处理http响应。

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

	Assert.notNull(url, "URI is required");
	Assert.notNull(method, "HttpMethod is required");
	ClientHttpResponse response = null;
	try {
		// 创建httpRequest
		ClientHttpRequest request = createRequest(url, method);
		if (requestCallback != null) {
			// HttpEntityRequestCallback#doWithRequest
			requestCallback.doWithRequest(request);
		}
		// http执行
		response = request.execute();
		// http响应处理
		handleResponse(url, method, response);
		return (responseExtractor != null ? responseExtractor.extractData(response) : null);
	}
	catch (IOException ex) {
		String resource = url.toString();
		String query = url.getRawQuery();
		resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
		throw new ResourceAccessException("I/O error on " + method.name() +
				" request for \"" + resource + "\": " + ex.getMessage(), ex);
	}
	finally {
		if (response != null) {
		    // 关闭流
			response.close();
		}
	}
}

2. httpRequest 创建

在这里插入图片描述
Restemplate继承了InterceptingHttpAccessor,使用的是InterceptingClientHttpRequestFactory创建httpRequest。

public ClientHttpRequestFactory getRequestFactory() {
	// ClientHttpRequestInterceptor 支持拓展
	List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
	if (!CollectionUtils.isEmpty(interceptors)) {
		ClientHttpRequestFactory factory = this.interceptingRequestFactory;
		if (factory == null) {
		    // 创建默认的HttpRequestFactory
			factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
			this.interceptingRequestFactory = factory;
		}
		return factory;
	}
	else {
		return super.getRequestFactory();
	}
}
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
	return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
}

3. doWithRequest

调用HttpEntityRequestCallback的doWithRequest,若body为null,主要是设置header。若body补位null,则主要是通过根据body的类型匹配HttpMessageConverter,将body序列化为httpRequest中

public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
	// 调用父类的doWithRequest方法
	super.doWithRequest(httpRequest);
	Object requestBody = this.requestEntity.getBody();
	if (requestBody == null) {
		HttpHeaders httpHeaders = httpRequest.getHeaders();
		HttpHeaders requestHeaders = this.requestEntity.getHeaders();
		// 将HttpEntity中的header复制到httpRequest中
		if (!requestHeaders.isEmpty()) {
			requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
		}
		// 设置ContentLength
		if (httpHeaders.getContentLength() < 0) {
			httpHeaders.setContentLength(0L);
		}
	}
	else {
		Class<?> requestBodyClass = requestBody.getClass();
		Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
				((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
		HttpHeaders httpHeaders = httpRequest.getHeaders();
		HttpHeaders requestHeaders = this.requestEntity.getHeaders();
		MediaType requestContentType = requestHeaders.getContentType();
		// getMessageConverters()中的MessageConverters是在RestTemplate构造函数中添加的
		for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
			// 依次轮询每个messageConverter判断是否支持写当前httpRequestBody
			if (messageConverter instanceof GenericHttpMessageConverter) {
				GenericHttpMessageConverter<Object> genericConverter =
						(GenericHttpMessageConverter<Object>) messageConverter;
				if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
					// header复制
					if (!requestHeaders.isEmpty()) {
						requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
					}
					// debug级别打印body信息
					logBody(requestBody, requestContentType, genericConverter);
					// body序列化写入httpRequest
					genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
					return;
				}
			}
			else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
				// header复制
				if (!requestHeaders.isEmpty()) {
					requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
				}
				// debug日志级别打印body信息
				logBody(requestBody, requestContentType, messageConverter);
				// body序列化写入httpRequest
				((HttpMessageConverter<Object>) messageConverter).write(
						requestBody, requestContentType, httpRequest);
				return;
			}
		}
		String message = "No HttpMessageConverter for " + requestBodyClass.getName();
		if (requestContentType != null) {
			message += " and content type \"" + requestContentType + "\"";
		}
		throw new RestClientException(message);
	}
}

4. execute

前面说到默认创建的是InterceptingClientHttpRequest,接下来看看InterceptingClientHttpRequest的继承关系如下:
在这里插入图片描述
实际执行http请求就是executeInternal,但是从InterceptingClientHttpRequest的名字可以看出来,这个httpRequest实际上是一个调用链模式,可以根据需要拓展不同的HttpRequest,实现不同的功能,如常用的LoadBalancerInterceptor,也就是利用这个拓展能力实现LoadBalancer。

@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
	InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
	return requestExecution.execute(this, bufferedOutput);
}


private class InterceptingRequestExecution implements ClientHttpRequestExecution {

	private final Iterator<ClientHttpRequestInterceptor> iterator;

	public InterceptingRequestExecution() {
		this.iterator = interceptors.iterator();
	}

	@Override
	public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
		if (this.iterator.hasNext()) {
			// 调用链依次调用ClientHttpRequestInterceptor
			ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
			return nextInterceptor.intercept(request, body, this);
		}
		else {
			HttpMethod method = request.getMethod();
			Assert.state(method != null, "No standard HTTP method");
			// 创建最终执行http请求的ClientHttpRequest,factory默认为SimpleClientHttpRequestFactory,创建SimpleBufferingClientHttpRequest
			ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
			// header全部复制
			request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
			// body复制
			if (body.length > 0) {
				if (delegate instanceof StreamingHttpOutputMessage) {
					StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
					streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
				}
				else {
					StreamUtils.copy(body, delegate.getBody());
				}
			}
			// 最终http请求
			return delegate.execute();
		}
	}
}

SimpleBufferingClientHttpRequest的继承关系
在这里插入图片描述
SimpleBufferingClientHttpRequest实际执行方法

protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
	// 复制header,是否全部复制由系统参数sun.net.http.allowRestrictedHeaders决定
	// connection 为HttpURLConnection
	addHeaders(this.connection, headers);
	// JDK <1.8 doesn't support getOutputStream with HTTP DELETE
	if (getMethod() == HttpMethod.DELETE && bufferedOutput.length == 0) {
		this.connection.setDoOutput(false);
	}
	if (this.connection.getDoOutput() && this.outputStreaming) {
		this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
	}
	// http连接服务端
	this.connection.connect();
	if (this.connection.getDoOutput()) {
		// 若有body则发送整个请求,并获取请求结果
		FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
	}
	else {
		// 发送http请求
		this.connection.getResponseCode();
	}
	return new SimpleClientHttpResponse(this.connection);
}

5. http响应解析

首先调用handleResponse处理异常情况,再调用ResponseEntityResponseExtractor中的HttpMessageConverterExtractor对响应进行装换。

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);
	}
}

private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {
	@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;
		}
	}

	@Override
	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();
		}
	}
}
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)) {
						if (logger.isDebugEnabled()) {
							ResolvableType resolvableType = ResolvableType.forType(this.responseType);
							logger.debug("Reading to [" + resolvableType + "]");
						}
						return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
					}
				}
				if (this.responseClass != null) {
					if (messageConverter.canRead(this.responseClass, contentType)) {
						if (logger.isDebugEnabled()) {
							String className = this.responseClass.getName();
							logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
						}
						return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
					}
				}
			}
		}
		catch (IOException | HttpMessageNotReadableException ex) {
			throw new RestClientException("Error while extracting response for type [" +
					this.responseType + "] and content type [" + contentType + "]", ex);
		}

		throw new UnknownContentTypeException(this.responseType, contentType,
				responseWrapper.getRawStatusCode(), responseWrapper.getStatusText(),
				responseWrapper.getHeaders(), getResponseBody(responseWrapper));
	}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/74774.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

paddleOCR识别问题和解决方案

常见问题解答&#xff1a; 文本检测相关FAQ paddle ocr 常见问答 https://aistudio.baidu.com/aistudio/projectdetail/4491412 参数介绍 import argparse import os import sys import cv2 import numpy as np import paddle from PIL import Image, ImageDraw, ImageFont …

如何基于 APISIX 迭代数字智联平台

分享嘉宾&#xff1a;沈巍&#xff0c;浙大网新研发总监。 网新电气成立于 2011 年&#xff0c;属于浙大网新旗下&#xff0c;为绿色智慧交通系统解决方案的提供商。业务范围包括铁路、隧道、城市智能交通、航空、高速公路等行业。整个高铁信息化的业务分布占到了全国市场的 20…

Electron 麒麟 Linux 系统 root 账户报错

使用Electron打包成客户端在麒麟Linux 操作系统上运行&#xff0c;普通用户启动程序正常 使用root用户出现各种问题。总结问题如下&#xff1a; 1. Running as root without --no-sandbox is not supported。 解决方案&#xff1a; 在启动命令后面加入 --no-sandbox sudo …

为SSH远程配置固定的公网TCP端口地址【内网穿透】

由于使用免费的cpolar生成的公网地址&#xff0c;为随机临时地址&#xff0c;24小时内会发生变化&#xff0c;并且带宽较小&#xff0c;只有1M。对于需要长期SSH远程的用户来说&#xff0c;配置固定的公网TCP端口地址&#xff0c;提高带宽就很有必要。 1. 保留一个固定TCP端口地…

信息收集道道之外网信息收集

#信息收集道道之外网信息收集 从个人的角度去简单整理下打点前的信息收集那些事。从信息收集本质上来说多数内容都是大同小异&#xff0c;遇到坚壁时&#xff0c;不用死磕&#xff0c;毕竟条条大路通罗马&#xff08;大佬们也可以说说看法~向各位大佬学习&#xff01; 红队知…

业务数据LEFT JOIN 多表查询慢--优化操作

首先你会想到&#xff0c;给表加索引&#xff0c;那么mysql会给主键自动建立索引吗? 会的&#xff0c;当然会。 在我们查询的业务表操作的时候&#xff0c;表业务数据庞大起来的时候&#xff0c;以及left join多的时候&#xff0c;甚至多表关联到几十张表的时候&#xff0c;查…

【云原生】二进制部署k8s集群(中)搭建node节点

内容预知 连接上文 1. 部署 Worker Node 组件 1.1 work node 组件部署前需了解的节点注册机制 1.2 Worker Node 组件部署步骤 2. k8s的CNI网络插件模式 2.1 k8s的三种网络模式 K8S 中 Pod 网络通信&#xff1a; &#xff08;1&#xff09;Pod 内容器与容器之间的通信 &am…

2022年四川建筑八大员(标准员)考试试题及答案

百分百题库提供建筑八大员&#xff08;标准员&#xff09;考试试题、建筑八大员&#xff08;标准员&#xff09;考试真题、建筑八大员&#xff08;标准员&#xff09;证考试题库等,提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 1.施工项目管理目标…

[附源码]Python计算机毕业设计SSM基于框架的校园爱心公益平台的设计与实现(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

java基于Springboot的学生毕业离校系统-计算机毕业设计

项目介绍 学生毕业离校系统的开发过程中&#xff0c;采用B / S架构&#xff0c;主要使用Java技术进行开发&#xff0c;结合最新流行的springboot框架。中间件服务器是Tomcat服务器&#xff0c;使用Mysql数据库和Eclipse开发环境。该学生毕业离校系统包括管理员、学生和教师。其…

【SpringBoot应用篇】SpringBoot+JasperReport导出PDF

【SpringBoot应用篇】SpringBootJasperReport导出PDFJasperReport简介JasperReport的开发步骤生命周期执行流程模板工具Jaspersoft Studio概述安装配置面板介绍基本使用模板制作编译模板入门案例环境准备导出一个最基本的PDF文件导出用户列表需求数据直接从数据库中获取数据从后…

Lombok实现原理解析

文章目录前言一、Lombok注解分析二、编译期的注解处理期三、Lombok使用方法四、自定义注解处理器1、自定义注解2、实现Processor接口3、注册注解处理器五、实战MyGetter注解1、新建Maven工程myLombok2、新建子模块myget3、新建子模块person4、编译并查看结果总结前言 相信做ja…

171-有趣的OpenAI的chatGPT小实验

最近玩了一下chatGPT 问了他很多问题 然后我问了一个问题 帮我想10个帮女朋友过生日的办法 然后AI就回复了我10种 然后我继续问了我说再来10个 他又想了10种&#xff0c; 所以我特别想看看他到底有没有极限 10个 20个 30个 40个 50个 60个 70个 80个 90个 100个 接下去…

秋招---SQL学习

文章目录SQL的执行顺序一般是怎样的SQL如何性能优化1.select尽量不要查询全部*&#xff0c;而是查具体字段2.避免在where子句中使用 or 来连接条件3.尽量使用数值替代字符串类型tinyint,int,bigint,smallint类型4.用varchar代替char那什么时候要用char不用varchar呢链接&#x…

玩转华夏数艺

这里写自定义目录标题华夏数艺简述新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是…

叶酸-葡聚糖-盐酸吡柔比星偶联物(FA-PRB-DEX-NPs)|丝裂霉素C-右旋糖酐交联物(MMC-D)

叶酸-葡聚糖-盐酸吡柔比星偶联物(FA-PRB-DEX-NPs) 产品描述&#xff1a;将葡聚糖,盐酸吡柔比星和叶酸按序化学合成,并进一步定量,采用体外细胞性实验(四甲基偶氮唑蓝法),观察游离盐酸吡柔比星,叶酸-葡聚糖-盐酸吡柔比星,叶酸-葡聚糖-盐酸吡柔比星游离叶酸对于不同浓度细胞株SG…

计算机毕业设计ssm+vue基本微信小程序的育教幼教知识学习系统 uniapp 小程序

项目介绍 随着互联网技术的发发展,计算机技术广泛应用在人们的生活中,逐渐成为日常工作、生活不可或缺的工具,各种管理系统层出不穷。时代对人们的知识水平和综合素质要求也越来越高了,因此出现了各种适合用户在线学习系统。广泛存在于PC系统,手机APP,电脑软件等等,其中用户量…

Effective C++条款29:为“异常安全”而努力是值得的(Strive for exception-safe code)

Effective C条款29&#xff1a;为“异常安全”而努力是值得的&#xff08;Strive for exception-safe code&#xff09;条款29&#xff1a;为“异常安全”而努力是值得的1、抛出异常的案例2、解决资源泄露的问题3、异常安全的三种保证4、两种解决异常安全的方法4.1 使用智能指针…

如何压缩动态图片大小?gif图太大了怎么压缩?

对于新媒体行业人员来说&#xff0c;平时在工作中需要存非常多的素材&#xff0c;这些素材中有很多就是gif格式的&#xff0c;随着积累的素材越来越多&#xff0c;这些素材会占用大量的储存空间&#xff0c;那么遇到这种情况应该怎么办呢&#xff1f;应该如何压缩动态图片大小&…

Flutter - 布局原理与约束(constraints)

尺寸限制类容器用于限制容器大小&#xff0c;Flutter中提供了多种这样的容器&#xff0c;如ConstrainedBox、SizedBox、UnconstrainedBox、AspectRatio 等 1 ConstrainedBox ConstrainedBox 用于对子组件添加额外的约束 一般作为最外层的父布局 2 BoxConstraints BoxConstrai…