jvm参数造成http请求Read time out

news2024/9/20 8:47:24

问题描述

线上部署的代码,部署在测试环境突然抛出接口请求Read time out的异常。查看线上日志,接口请求正常。重新启动,部署测试环境代码都没有效果,接口还是必现Read time out。

原因分析:

1. 排查网络原因

直接在测试环境服务器上执行使用curl命令执行http请求,接口正常返回,所以排查网络,第三方服务的原因。

2. 网络抓包

解决这类问题,最简单的方法就是抓一个正常的http请求包,抓一个Read time out异常的http请求包,对比两个的报文。但是事与愿违,容器环境禁用tcpdump命令,也就抓不了包。

3. 本地代码debug

启动本地代码连接测试环境配置,再出发相同的请求,接口返回正常。

4. 调整日志等级

项目统一使用spring cloud体系微服务架构,将日志等级该为debug,日志会打印一些http请求信息。观查到报错前的http请求日志如下:

2022-12-05 09:47:21.209 DEBUG 1 --- [http-nio-8080-exec-44] s.n.www.protocol.http.HttpURLConnection  : [TxId : xxx-1^1670143309480^7363 , SpanId : 1300305726613613085] sun.net.www.MessageHeader@11ef701319 pairs: 
{GET /xxxx/audiorid?recordId=9776d4f1-8b89-488a-b00c-cd430f39c003&recordDate=1670146625251 HTTP/1.1: null}
{Accept: application/octet-stream, application/json, application/*+json, */*}
{System-Id: xxxapi}
{sign: xxxxxxxxxxxx}
{Sign-Type: RSA}
{Sign-Timestamp: 1670204841194}
{Content-Type: application/x-www-form-urlencoded}
{Content-Length: 135}
{request-source-app: xxxapi}
{Pinpoint-TraceID: xxx-1^1670143309480^7363}
{Pinpoint-SpanID: 3740963636577534541}
{Pinpoint-pSpanID: 1300305726613613085}
{Pinpoint-Flags: 0}{Pinpoint-pAppName: xxxapi}
{Pinpoint-pAppType: 1210}
{Pinpoint-Host: xxx.com}
{User-Agent: Java/1.8.0_231}
{Host: xxxxx.com}
{Connection: keep-alive}

因为本接口Method是GET,Content-Length应该为0,但是日志缺显示为135

5. 排查代码

http代码是基于restTemplate封装抽象统一实现的,以下是执行get请求的抽象方法:

public String executeGet(String systemName, String requestUrl, Map<String,Object> params, Map<String,Object> headerParams){
	HttpEntity<MutiValueMap<String, String>> entity = RestHttpUtils.getEntity(params, headerParams);
	return execute(systemName, requestUrl, HttpMethod.GET, entity, params);
}

其中装换entity有点问题

public static HttpEntity<MultiValueMap<String,String>> getEntity(Map<String,Object> params, Map<String,Object> headerParams){
	MultiValueMap<String,String> mvm = getMultiValueMap(params);
	HttpHeaders headers = getHeaders(headersParams);
	return new HttpEntity<MultiValueMap<String,String>>(mvm, headers);
}

在HttpEntity中,param被赋值给body,get请求中的param应该是queryParam,不应该在body中。现在赋值到Body,那么Content-Length就会有值。

public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) {
		this.body = body;
		this.headers = HttpHeaders.readOnlyHttpHeaders(headers != null ? headers : new HttpHeaders());
	}

Content-Length在restTemplate#doExecute中进行赋值

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 {
		ClientHttpRequest request = createRequest(url, method);
		if (requestCallback != null) {
			requestCallback.doWithRequest(request);
		}
		response = request.execute();
		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();
		}
	}
}

具体见requestCallback.doWithRequest(request),有body有值,所以Content-Length 没有赋值为0,而是在converter中序列化body并赋值。

public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
		super.doWithRequest(httpRequest);
		Object requestBody = this.requestEntity.getBody();
		if (requestBody == null) {
			HttpHeaders httpHeaders = httpRequest.getHeaders();
			HttpHeaders requestHeaders = this.requestEntity.getHeaders();
			if (!requestHeaders.isEmpty()) {
				requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
			}
			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();
			for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
				if (messageConverter instanceof GenericHttpMessageConverter) {
					GenericHttpMessageConverter<Object> genericConverter =
							(GenericHttpMessageConverter<Object>) messageConverter;
					if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
						if (!requestHeaders.isEmpty()) {
							requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
						}
						logBody(requestBody, requestContentType, genericConverter);
						genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
						return;
					}
				}
				else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
					if (!requestHeaders.isEmpty()) {
						requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
					}
					logBody(requestBody, requestContentType, messageConverter);
					((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);
		}
	}

body的数据类型是MultiValueMap<String,String,对应的converter是AllEncompassingFormHttpMessageConverter

public void write(MultiValueMap<String, ?> map, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {

	if (isMultipart(map, contentType)) {
		writeMultipart((MultiValueMap<String, Object>) map, contentType, outputMessage);
	}
	else {
		writeForm((MultiValueMap<String, Object>) map, contentType, outputMessage);
	}
}
	
private void writeForm(MultiValueMap<String, Object> formData, @Nullable MediaType contentType,
			HttpOutputMessage outputMessage) throws IOException {

	contentType = getFormContentType(contentType);
	outputMessage.getHeaders().setContentType(contentType);

	Charset charset = contentType.getCharset();
	Assert.notNull(charset, "No charset"); // should never occur

	byte[] bytes = serializeForm(formData, charset).getBytes(charset);
	outputMessage.getHeaders().setContentLength(bytes.length);

	if (outputMessage instanceof StreamingHttpOutputMessage) {
		StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
		streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(bytes, outputStream));
	}
	else {
		StreamUtils.copy(bytes, outputMessage.getBody());
	}
}

虽然在这里进行赋值,但是代码一直以来缺正常运行。继续跟踪restTemplate代码。restTemplate第一层使用的是InterceptingClientHttpRequest,使用pipeline的模式,支持对http请求拓展。

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 nextInterceptor = this.iterator.next();
			return nextInterceptor.intercept(request, body, this);
		}
		else {
			HttpMethod method = request.getMethod();
			Assert.state(method != null, "No standard HTTP method");
			ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
			request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
			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());
				}
			}
			return delegate.execute();
		}
	}
}

真正执行http请求的是SimpleBufferingClientHttpRequest

@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 (getMethod() == HttpMethod.DELETE && 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);
}

static void addHeaders(HttpURLConnection connection, HttpHeaders headers) {
	String method = connection.getRequestMethod();
	if (method.equals("PUT") || method.equals("DELETE")) {
		if (!StringUtils.hasText(headers.getFirst(HttpHeaders.ACCEPT))) {
			// Avoid "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
			// from HttpUrlConnection which prevents JSON error response details.
			headers.set(HttpHeaders.ACCEPT, "*/*");
		}
	}
	headers.forEach((headerName, headerValues) -> {
		if (HttpHeaders.COOKIE.equalsIgnoreCase(headerName)) {  // RFC 6265
			String headerValue = StringUtils.collectionToDelimitedString(headerValues, "; ");
			connection.setRequestProperty(headerName, headerValue);
		}
		else {
			for (String headerValue : headerValues) {
				String actualHeaderValue = headerValue != null ? headerValue : "";
				connection.addRequestProperty(headerName, actualHeaderValue);
			}
		}
	});
}

其中addHeaders(this.connection, headers)就是对header进行处理的地方,这里的connection就是HttpURLConnection

public synchronized void addRequestProperty(String var1, String var2) {
       if (!this.connected && !this.connecting) {
           if (var1 == null) {
               throw new NullPointerException("key is null");
           } else {
               if (this.isExternalMessageHeaderAllowed(var1, var2)) {
                   this.requests.add(var1, var2);
                   if (!var1.equalsIgnoreCase("Content-Type")) {
                       this.userHeaders.add(var1, var2);
                   }
               }

           }
       } else {
           throw new IllegalStateException("Already connected");
       }
   }
private boolean isExternalMessageHeaderAllowed(String var1, String var2) {
       this.checkMessageHeader(var1, var2);
       return !this.isRestrictedHeader(var1, var2);
   }
private boolean isRestrictedHeader(String var1, String var2) {
       if (allowRestrictedHeaders) {
           return false;
       } else {
           var1 = var1.toLowerCase();
           if (restrictedHeaderSet.contains(var1)) {
               return !var1.equals("connection") || !var2.equalsIgnoreCase("close");
           } else {
               return var1.startsWith("sec-");
           }
       }
   }

注意这里有个isExternalMessageHeaderAllowed判断,条件成立的情况下,才往this.requests中添加header。

static {
      String var0 = (String)AccessController.doPrivileged(new GetPropertyAction("http.agent"));
       if (var0 == null) {
           var0 = "Java/" + version;
       } else {
           var0 = var0 + " Java/" + version;
       }

       userAgent = var0;
       String var1 = getNetProperty("jdk.http.auth.tunneling.disabledSchemes");
       disabledTunnelingSchemes = schemesListToSet(var1);
       var1 = getNetProperty("jdk.http.auth.proxying.disabledSchemes");
       disabledProxyingSchemes = schemesListToSet(var1);
       validateProxy = (Boolean)AccessController.doPrivileged(new GetBooleanAction("http.auth.digest.validateProxy"));
       validateServer = (Boolean)AccessController.doPrivileged(new GetBooleanAction("http.auth.digest.validateServer"));
       enableESBuffer = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.net.http.errorstream.enableBuffering"));
       timeout4ESBuffer = (Integer)AccessController.doPrivileged(new GetIntegerAction("sun.net.http.errorstream.timeout", 300));
       if (timeout4ESBuffer <= 0) {
           timeout4ESBuffer = 300;
       }

       bufSize4ES = (Integer)AccessController.doPrivileged(new GetIntegerAction("sun.net.http.errorstream.bufferSize", 4096));
       if (bufSize4ES <= 0) {
           bufSize4ES = 4096;
       }

       allowRestrictedHeaders = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.net.http.allowRestrictedHeaders"));
       if (!allowRestrictedHeaders) {
           restrictedHeaderSet = new HashSet(restrictedHeaders.length);

           for(int var2 = 0; var2 < restrictedHeaders.length; ++var2) {
               restrictedHeaderSet.add(restrictedHeaders[var2].toLowerCase());
           }
       } else {
           restrictedHeaderSet = null;
       }

       EXCLUDE_HEADERS = new String[]{"Proxy-Authorization", "Authorization"};
       EXCLUDE_HEADERS2 = new String[]{"Proxy-Authorization", "Authorization", "Cookie", "Cookie2"};
       logger = PlatformLogger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
   }

最为关键的就是allowRestrictedHeaders根据sun.net.http.allowRestrictedHeaders参数读取的,一开始在pinpoint上面看应用的参数没有设置这个值,所以又浪费了一些时间,最终使用jinfo命令看到测试环境这个配置项为true,而开发环境是false。
在这里插入图片描述

解决方案:

简单的解决方法就是启动的时候将sun.net.http.allowRestrictedHeaders配置为false,后面将queryParam赋值给body的代码去掉。

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

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

相关文章

内核开发-同步场景与概念

进程上下文执行环境还有中断上下文执行环境&#xff0c;并且中断上下文优先级比较高&#xff0c;可以随时打断进程的执行&#xff0c;因此情况更加复杂。内核当中提供了不同的同步机制。比如说信号量&#xff0c;自旋锁&#xff0c;rcu&#xff0c;原子变量等等。他们各自都有自…

《计算机视觉技术与应用》-----第六章 直方图

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…

软件过程模型

软件过程软件过程:获得高质量软件的一系列任务框架瀑布模型:特点:顺序,依赖,推迟实现,质量保证优点:规范方法,规定文档,阶段质量验证缺点:开发初期困难,需求验证困难,难以维护快速原型优点:满足需求,线性过程缺点:设计困难,原型理解不同,不利于创新增量模型:优点:短时间可完成部…

[附源码]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…

[附源码]Python计算机毕业设计Django体育馆场地预约管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

VSCode 配置C语言环境 全程记录 ,配置成功

目录 1.vscode介绍&#xff1a; 1.1 卸载干净VSCode 1.2安装VSCode 1.2.1 下载安装 1.2.2 vscode 小插件安装 2. 配置vscode 编译器 2.1 下载编译器资源文件&#xff1a; 2.2 配置环境变量 2.3 vscode项目文件配置 1. 首先新建一个.c文件&#xff0c;命名为英文哦 2. 然后…

含有DBCO和马来酰亚胺基团Mal-PEG2-DBCO,2698339-31-8,DBCO-PEG2-Maleimide

中英文别名&#xff1a; CAS号&#xff1a;2698339-31-8 | 英文名&#xff1a;DBCO-PEG2-Maleimide&#xff0c;Mal-PEG2-DBCO |中文名&#xff1a;二苯并环辛炔-二聚乙二醇-马来酰亚胺物理参数&#xff1a; CASNumber&#xff1a;2698339-31-8 Molecular formula&#xff1a;C…

工业和信息化部公布45个国家先进制造业集群名单

近日&#xff0c;工业和信息化部正式公布45个国家先进制造业集群的名单。 45个国家级集群2021年主导产业产值达19万亿元&#xff0c;布局建设了18家国家制造业创新中心&#xff0c;占全部国家级创新中心数量的70%&#xff0c;拥有国家级技术创新载体1700余家&#xff0c;培育创…

【苹果相册推iMessage】软件安装Websocket可以在浏览器顶用于支持两个通讯并使用它

推荐内容IMESSGAE相关 作者推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者推荐内容3.日历推 *** 点击即可查看作者要求内容信息作者推荐…

Python3,9行代码,对比两个Excel数据差异,并把差异结果重新保存。

Excel数据差异对比1、引言2、代码实战3、总结1、引言 小屌丝&#xff1a;鱼哥&#xff0c;还记得上次写的把数据库的查询结果写入到excel这个脚本不。 小鱼&#xff1a;嗯… 可以说不记得吗 小屌丝&#xff1a;我猜你就记得。 小鱼&#xff1a;你…说…啥&#xff1f;&#xf…

画布的使用方式

一、’注册—登录—主页’作业中的技术点&#xff1a; 1、前端&#xff1a; ​ &#xff08;1&#xff09;input file标签的样式处理 ​ &#xff08;2&#xff09;文件类型的设置&#xff1a;accept属性来指定 ​ &#xff08;3&#xff09;input file选择的图片立即显示出…

搭建K8s集群

前言&#xff1a;本次将通过kubeadm部署1个master节点&#xff0c;2个worker节点K8s集群&#xff0c;本次集群使用的容器运行工具为docker(题外话&#xff1a;K8s的容器运行工具也可以用docker、containerd、cio等等&#xff0c;其中containerd是一个轻量级、工业级的容器运行工…

aws eks 使用paker构建自定义ami

资料 如何创建用于 Amazon EKS 的自定义 Amazon Linux AMI&#xff1f; 构建预置容器镜像的EKS自定义AMI解决方案 https://github.com/awslabs/amazon-eks-ami https://github.com/awslabs/amazon-eks-ami/blob/master/doc/USER_GUIDE.md https://developer.hashicorp.com/…

[Cortex-M3]-3-分散加载文件解析(.sct)

目录 1 分散加载文件.sct 2 如何生成.sct文件 3 *(InRoot$$Sections) 说明 4 如何修改分散加载文件 5 已经初始化变量的初值&#xff0c;存储位置 6 RW ZI和RO如何执行 1 分散加载文件.sct MDK的分散加载主要是通过.sct文件实现的&#xff0c;链接器根据.sct…

黄菊华老师,Python毕业设计毕设辅导教程(2):Python开发准备,Window 平台安装 Python

Python3 开发准备 Python3 可应用于多平台包括 Windows、Linux 和 Mac OS X。 Unix (Solaris, Linux, FreeBSD, AIX, HP/UX, SunOS, IRIX, 等等。)Win 9x/NT/2000Macintosh (Intel, PPC, 68K)OS/2DOS (多个DOS版本)PalmOSNokia 移动手机Windows CEAcorn/RISC OSBeOSAmigaVMS/…

网络安全专业学习路线

​最专业、全面的网络安全学习路线来咯~&#xff08;虽然是网络安全学习路线&#xff0c;但重心还是在Web安全上&#xff09; 展示学习路线之前&#xff0c;建议大家先了解一下这几个问题&#xff0c;既是认清形势&#xff0c;也是认清自我&#xff1a; 为什么要学网络安全&a…

二 TypeScript 基础(初识和语法)

变量 什么是变量 变量是存储信息的容器,其中的值(内容)是可变的。 变量的声明 我们使用var关键字来声明变量&#xff0c;新的关键字let来声明带有(块级作用域)属性的变量。 var a 或者 let a 使用 来赋值 var a 1; //如果只声明变量,井没有赋值的话,该变量的值为 undef…

初识TypeScript编译器(tsc)

须知少时凌云志&#xff0c; 曾许人间第一流。 哪晓岁月蹉跎过&#xff0c; 依旧名利俩无收。 文章目录1. 安装 TypeScript2. 熟悉tsc的编译选项3. 编译.ts文件4. TS报错后那JS呢4.1 通过noEmitOnError禁止在出错的情况下编译出JS目标文件5. 查看编译生成的JS文件5.1 通过targe…

数据库分库分表方案

一、数据库瓶颈 不管是IO瓶颈&#xff0c;还是CPU瓶颈&#xff0c;最终都会导致数据库的活跃连接数增加&#xff0c;进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是&#xff0c;可用数据库连接少甚至无连接可用。接下来就可以想象了吧&#xff08;并发…

文华财经期货量化短线策略支撑压力指标公式,短线行情无未来函数多空均线红涨绿跌信号

很多人从行情历史走势图发现&#xff0c;金叉行情上涨&#xff0c;死叉行情下跌的规律&#xff0c;告诉大家那些完美冬形只是过去式而已&#xff0c;行情震荡时&#xff0c;指标失灵可以把你震荡到爆仓。我们也会经常发现不好的指标在很多情况下会相互矛盾&#xff0c;这个指标…