本文主要研究一下httpclient的connect timeout异常

实例代码

@Test

public void testConnectTimeout() throws IOException, InterruptedException {

HttpClient client = HttpClient.newBuilder()

.build();

HttpRequest request = HttpRequest.newBuilder()

.uri(URI.create("https://twitter.com"))

.build();

long start = System.currentTimeMillis();

try{

HttpResponse result = client.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println(result.body());

}finally {

long cost = System.currentTimeMillis() - start;

System.out.println("cost:"+cost);

}

}

异常日志如下:

cost:75814

java.net.ConnectException: Operation timed out

at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:561)

at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)

at com.example.HttpClientTest.testConnectTimeout(HttpClientTest.java:464)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:566)

at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)

at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)

at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)

at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)

at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)

at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)

at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)

at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)

at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)

at org.junit.runners.ParentRunner.run(ParentRunner.java:363)

at org.junit.runner.JUnitCore.run(JUnitCore.java:137)

at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)

at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)

at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)

at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.base/java.lang.reflect.Method.invoke(Method.java:566)

at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)

Caused by: java.net.ConnectException: Operation timed out

at java.base/sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)

at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:779)

at java.net.http/jdk.internal.net.http.PlainHttpConnection$ConnectEvent.handle(PlainHttpConnection.java:128)

at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.handleEvent(HttpClientImpl.java:957)

at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.lambda$run$3(HttpClientImpl.java:912)

at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)

at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:912)

Exchange.responseAsync

java.net.http/jdk/internal/net/http/Exchange.java

public CompletableFuture responseAsync() {

return responseAsyncImpl(null);

}

CompletableFuture responseAsyncImpl(HttpConnection connection) {

SecurityException e = checkPermissions();

if (e != null) {

return MinimalFuture.failedFuture(e);

} else {

return responseAsyncImpl0(connection);

}

}

CompletableFuture responseAsyncImpl0(HttpConnection connection) {

Function, CompletableFuture> after407Check;

bodyIgnored = null;

if (request.expectContinue()) {

request.addSystemHeader("Expect", "100-Continue");

Log.logTrace("Sending Expect: 100-Continue");

// wait for 100-Continue before sending body

after407Check = this::expectContinue;

} else {

// send request body and proceed.

after407Check = this::sendRequestBody;

}

// The ProxyAuthorizationRequired can be triggered either by

// establishExchange (case of HTTP/2 SSL tunneling through HTTP/1.1 proxy

// or by sendHeaderAsync (case of HTTP/1.1 SSL tunneling through HTTP/1.1 proxy

// Therefore we handle it with a call to this checkFor407(...) after these

// two places.

Function, CompletableFuture> afterExch407Check =

(ex) -> ex.sendHeadersAsync()

.handle((r,t) -> this.checkFor407(r, t, after407Check))

.thenCompose(Function.identity());

return establishExchange(connection)

.handle((r,t) -> this.checkFor407(r,t, afterExch407Check))

.thenCompose(Function.identity());

}

// get/set the exchange impl, solving race condition issues with

// potential concurrent calls to cancel() or cancel(IOException)

private CompletableFuture extends ExchangeImpl>

establishExchange(HttpConnection connection) {

if (debug.on()) {

debug.log("establishing exchange for %s,%n\t proxy=%s",

request, request.proxy());

}

// check if we have been cancelled first.

Throwable t = getCancelCause();

checkCancelled();

if (t != null) {

return MinimalFuture.failedFuture(t);

}

CompletableFuture extends ExchangeImpl> cf, res;

cf = ExchangeImpl.get(this, connection);

// We should probably use a VarHandle to get/set exchangeCF

// instead - as we need CAS semantics.

synchronized (this) { exchangeCF = cf; };

res = cf.whenComplete((r,x) -> {

synchronized(Exchange.this) {

if (exchangeCF == cf) exchangeCF = null;

}

});

checkCancelled();

return res.thenCompose((eimpl) -> {

// recheck for cancelled, in case of race conditions

exchImpl = eimpl;

IOException tt = getCancelCause();

checkCancelled();

if (tt != null) {

return MinimalFuture.failedFuture(tt);

} else {

// Now we're good to go. Because exchImpl is no longer

// null cancel() will be able to propagate directly to

// the impl after this point ( if needed ).

return MinimalFuture.completedFuture(eimpl);

} });

}

responseAsync最后调用ExchangeImpl.get(this, connection)

ExchangeImpl.get

java.net.http/jdk/internal/net/http/ExchangeImpl.java

/**

* Initiates a new exchange and assigns it to a connection if one exists

* already. connection usually null.

*/

static CompletableFuture extends ExchangeImpl>

get(Exchange exchange, HttpConnection connection)

{

if (exchange.version() == HTTP_1_1) {

if (debug.on())

debug.log("get: HTTP/1.1: new Http1Exchange");

return createHttp1Exchange(exchange, connection);

} else {

Http2ClientImpl c2 = exchange.client().client2(); // #### improve

HttpRequestImpl request = exchange.request();

CompletableFuture c2f = c2.getConnectionFor(request, exchange);

if (debug.on())

debug.log("get: Trying to get HTTP/2 connection");

return c2f.handle((h2c, t) -> createExchangeImpl(h2c, t, exchange, connection))

.thenCompose(Function.identity());

}

}

这里调用Http2ClientImpl.getConnectionFor获取连接

Http2ClientImpl.getConnectionFor

java.net.http/jdk/internal/net/http/Http2ClientImpl.java

/**

* When HTTP/2 requested only. The following describes the aggregate behavior including the

* calling code. In all cases, the HTTP2 connection cache

* is checked first for a suitable connection and that is returned if available.

* If not, a new connection is opened, except in https case when a previous negotiate failed.

* In that case, we want to continue using http/1.1. When a connection is to be opened and

* if multiple requests are sent in parallel then each will open a new connection.

*

* If negotiation/upgrade succeeds then

* one connection will be put in the cache and the others will be closed

* after the initial request completes (not strictly necessary for h2, only for h2c)

*

* If negotiate/upgrade fails, then any opened connections remain open (as http/1.1)

* and will be used and cached in the http/1 cache. Note, this method handles the

* https failure case only (by completing the CF with an ALPN exception, handled externally)

* The h2c upgrade is handled externally also.

*

* Specific CF behavior of this method.

* 1. completes with ALPN exception: h2 negotiate failed for first time. failure recorded.

* 2. completes with other exception: failure not recorded. Caller must handle

* 3. completes normally with null: no connection in cache for h2c or h2 failed previously

* 4. completes normally with connection: h2 or h2c connection in cache. Use it.

*/

CompletableFuture getConnectionFor(HttpRequestImpl req,

Exchange> exchange) {

URI uri = req.uri();

InetSocketAddress proxy = req.proxy();

String key = Http2Connection.keyFor(uri, proxy);

synchronized (this) {

Http2Connection connection = connections.get(key);

if (connection != null) {

try {

if (connection.closed || !connection.reserveStream(true)) {

if (debug.on())

debug.log("removing found closed or closing connection: %s", connection);

deleteConnection(connection);

} else {

// fast path if connection already exists

if (debug.on())

debug.log("found connection in the pool: %s", connection);

return MinimalFuture.completedFuture(connection);

}

} catch (IOException e) {

// thrown by connection.reserveStream()

return MinimalFuture.failedFuture(e);

}

}

if (!req.secure() || failures.contains(key)) {

// secure: negotiate failed before. Use http/1.1

// !secure: no connection available in cache. Attempt upgrade

if (debug.on()) debug.log("not found in connection pool");

return MinimalFuture.completedFuture(null);

}

}

return Http2Connection

.createAsync(req, this, exchange)

.whenComplete((conn, t) -> {

synchronized (Http2ClientImpl.this) {

if (conn != null) {

try {

conn.reserveStream(true);

} catch (IOException e) {

throw new UncheckedIOException(e); // shouldn't happen

}

offerConnection(conn);

} else {

Throwable cause = Utils.getCompletionCause(t);

if (cause instanceof Http2Connection.ALPNException)

failures.add(key);

}

}

});

}

如果没有连接会新创建一个,走的是Http2Connection.createAsync

Http2Connection.createAsync

java.net.http/jdk/internal/net/http/Http2Connection.java

// Requires TLS handshake. So, is really async

static CompletableFuture createAsync(HttpRequestImpl request,

Http2ClientImpl h2client,

Exchange> exchange) {

assert request.secure();

AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)

HttpConnection.getConnection(request.getAddress(),

h2client.client(),

request,

HttpClient.Version.HTTP_2);

// Expose the underlying connection to the exchange's aborter so it can

// be closed if a timeout occurs.

exchange.connectionAborter.connection(connection);

return connection.connectAsync(exchange)

.thenCompose(unused -> connection.finishConnect())

.thenCompose(unused -> checkSSLConfig(connection))

.thenCompose(notused-> {

CompletableFuture cf = new MinimalFuture<>();

try {

Http2Connection hc = new Http2Connection(request, h2client, connection);

cf.complete(hc);

} catch (IOException e) {

cf.completeExceptionally(e);

}

return cf; } );

}

这里先是调用了HttpConnection.getConnection获取连接,然后调用connectAsync进行连接

AsyncSSLConnection

java.net.http/jdk/internal/net/http/AsyncSSLConnection.java

@Override

public CompletableFuture connectAsync(Exchange> exchange) {

return plainConnection

.connectAsync(exchange)

.thenApply( unused -> {

// create the SSLTube wrapping the SocketTube, with the given engine

flow = new SSLTube(engine,

client().theExecutor(),

client().getSSLBufferSupplier()::recycle,

plainConnection.getConnectionFlow());

return null; } );

}

这里委托给plainConnection.connectAsync

PlainHttpConnection.connectAsync

java.net.http/jdk/internal/net/http/PlainHttpConnection.java

@Override

public CompletableFuture connectAsync(Exchange> exchange) {

CompletableFuture cf = new MinimalFuture<>();

try {

assert !connected : "Already connected";

assert !chan.isBlocking() : "Unexpected blocking channel";

boolean finished;

connectTimerEvent = newConnectTimer(exchange, cf);

if (connectTimerEvent != null) {

if (debug.on())

debug.log("registering connect timer: " + connectTimerEvent);

client().registerTimer(connectTimerEvent);

}

PrivilegedExceptionAction pa =

() -> chan.connect(Utils.resolveAddress(address));

try {

finished = AccessController.doPrivileged(pa);

} catch (PrivilegedActionException e) {

throw e.getCause();

}

if (finished) {

if (debug.on()) debug.log("connect finished without blocking");

cf.complete(null);

} else {

if (debug.on()) debug.log("registering connect event");

client().registerEvent(new ConnectEvent(cf));

}

} catch (Throwable throwable) {

cf.completeExceptionally(Utils.toConnectException(throwable));

try {

close();

} catch (Exception x) {

if (debug.on())

debug.log("Failed to close channel after unsuccessful connect");

}

}

return cf;

}

这里如果client有设置connectTimeout的话,则会创建一个connectTimerEvent

调用chan.connect进行连接,如果连接未完成,则注册ConnectEvent

SocketChannelImpl.connect

java.base/sun/nio/ch/SocketChannelImpl.java

@Override

public boolean connect(SocketAddress sa) throws IOException {

InetSocketAddress isa = Net.checkAddress(sa);

SecurityManager sm = System.getSecurityManager();

if (sm != null)

sm.checkConnect(isa.getAddress().getHostAddress(), isa.getPort());

InetAddress ia = isa.getAddress();

if (ia.isAnyLocalAddress())

ia = InetAddress.getLocalHost();

try {

readLock.lock();

try {

writeLock.lock();

try {

int n = 0;

boolean blocking = isBlocking();

try {

beginConnect(blocking, isa);

do {

n = Net.connect(fd, ia, isa.getPort());

} while (n == IOStatus.INTERRUPTED && isOpen());

} finally {

endConnect(blocking, (n > 0));

}

assert IOStatus.check(n);

return n > 0;

} finally {

writeLock.unlock();

}

} finally {

readLock.unlock();

}

} catch (IOException ioe) {

// connect failed, close the channel

close();

throw SocketExceptions.of(ioe, isa);

}

}

通过Net.connect调用本地方法进行连接

ConnectEvent

java.net.http/jdk/internal/net/http/PlainHttpConnection.java

final class ConnectEvent extends AsyncEvent {

private final CompletableFuture cf;

ConnectEvent(CompletableFuture cf) {

this.cf = cf;

}

@Override

public SelectableChannel channel() {

return chan;

}

@Override

public int interestOps() {

return SelectionKey.OP_CONNECT;

}

@Override

public void handle() {

try {

assert !connected : "Already connected";

assert !chan.isBlocking() : "Unexpected blocking channel";

if (debug.on())

debug.log("ConnectEvent: finishing connect");

boolean finished = chan.finishConnect();

assert finished : "Expected channel to be connected";

if (debug.on())

debug.log("ConnectEvent: connect finished: %s Local addr: %s",

finished, chan.getLocalAddress());

// complete async since the event runs on the SelectorManager thread

cf.completeAsync(() -> null, client().theExecutor());

} catch (Throwable e) {

Throwable t = Utils.toConnectException(e);

client().theExecutor().execute( () -> cf.completeExceptionally(t));

close();

}

}

@Override

public void abort(IOException ioe) {

client().theExecutor().execute( () -> cf.completeExceptionally(ioe));

close();

}

}

SelectorManager对准备好的事件触发handle操作,对于ConnectEvent,就是调用ConnectEvent.handle

ConnectEvent的handle方法执行chan.finishConnect(),如果捕获到异常,则调用cf.completeExceptionally(t)

SocketChannelImpl.finishConnect

java.base/sun/nio/ch/SocketChannelImpl.java

@Override

public boolean finishConnect() throws IOException {

try {

readLock.lock();

try {

writeLock.lock();

try {

// no-op if already connected

if (isConnected())

return true;

boolean blocking = isBlocking();

boolean connected = false;

try {

beginFinishConnect(blocking);

int n = 0;

if (blocking) {

do {

n = checkConnect(fd, true);

} while ((n == 0 || n == IOStatus.INTERRUPTED) && isOpen());

} else {

n = checkConnect(fd, false);

}

connected = (n > 0);

} finally {

endFinishConnect(blocking, connected);

}

assert (blocking && connected) ^ !blocking;

return connected;

} finally {

writeLock.unlock();

}

} finally {

readLock.unlock();

}

} catch (IOException ioe) {

// connect failed, close the channel

close();

throw SocketExceptions.of(ioe, remoteAddress);

}

}

checkConnect是一个本地方法,如果是连接超时,则抛出java.net.ConnectException: Operation timed out

tcp连接syn超时(net.ipv4.tcp_syn_retries)

当client端与server端建立连接,client发出syn包,如果等待一定时间没有收到server端发来的SYN+ACK,则会进行重试,重试次数由具体由net.ipv4.tcp_syn_retries决定

/ # sysctl -a | grep tcp_syn_retries

sysctl: error reading key 'net.ipv6.conf.all.stable_secret': I/O error

net.ipv4.tcp_syn_retries = 6

sysctl: error reading key 'net.ipv6.conf.default.stable_secret': I/O error

sysctl: error reading key 'net.ipv6.conf.eth0.stable_secret': I/O error

sysctl: error reading key 'net.ipv6.conf.lo.stable_secret': I/O error

linux默认是6次,第一次发送等待2^0秒没收到回包则重试第一次,之后等待2^1,以此类推,第六次重试等待2^6秒,因此一共是1s+2s+4s+8s+16s+32s+64s=127s,因而在linux平台下,如果httpclient没有设置connect timeout,则依赖系统tcp的syn超时,即127s之后超时,java的本地调用抛出java.net.ConnectException: Operation timed out

小结

使用jdk httpclient进行连接,如果没有设置client的connectTimeout,则具体的超时时间依赖系统的tcp相关设置

如果client端sync发送超时,则依赖tcp_syn_retries的配置来决定本地方法抛出java.net.ConnectException: Operation timed out异常的时间

linux下默认tcp_syn_retries默认为6,即重试6次,一共需要1s+2s+4s+8s+16s+32s+64s=127s,若再没有收到server端发来的SYN+ACK则抛出java.net.ConnectException: Operation timed out异常

doc

java+connect+time+out_聊聊jdk httpclient的connect timeout异常相关推荐

  1. [case39]聊聊jdk httpclient的executor

    序 本文主要研究一下jdk httpclient的executor HttpClientImpl java.net.http/jdk/internal/net/http/HttpClientImpl. ...

  2. java.net.UnknownHostExce:No route to host: connect 和java.net.UnknownHostException: twechat.sclzsi.cn

    在老项目strust1 jdk 1.4的项目中 使用 HttpClient client = new HttpClient();GetMethod getMethod = new GetMethod( ...

  3. java基础学习总结一(java语言发展历史、jdk的下载安装以及配置环境变量)

    最近一段时间计划复习一下java基础知识,使用的视频课程是尚学堂高淇老师的,上课过程中的心得体会 直接总结一下,方便以后复习. 一:计算机语言的发展 1:机器语言,最原始的语言,主要有"01 ...

  4. Java 学习(1) ---JDK安装和配置环境变量

    一,Java 开发的第一步,就是安装JDK(Java Development ToolKit  Java开发工具包) JDK 是Java开发的核心,因为它包括Java 运行环境,工具包和命令.当我们安 ...

  5. jan java c 生成 dll_Java配置----JDK开发环境搭建及环境变量配置

    [声明] 欢迎转载,但请保留文章原始出处→_→ [正文] 1.安装JDK开发环境 开始安装JDK: 修改安装目录如下: 确定之后,单击"下一步". 注:当提示安装JRE时,可以选择 ...

  6. macbook配置java环境变量_Mac系统配置JDK环境变量

    1.安装 因为并非所有用户都用得着 Java ,所以在默认状态下 OS X 不预装 Java , 如果你需要的话可以手动安装. 到 Oracle 下载最新版的 Java 8 JDK 安装,安装目录可通 ...

  7. eclipse 64位_如何安装调试 Java开发工具Eclipse和JDK环境

    JRE是个运行环境,JDK是个开发环境.因此写Java程序的时候需要JDK,而运行Java程序的时候就需要JRE.而JDK里面已经包含了JRE,因此只要安装了JDK,就可以编辑Java程序,也可以正常 ...

  8. Linux java 生效不了,linux jdk 不生效怎么办

    [摘要] 操作系统(Operating System,简称OS)是管理计算机硬件与软件资源的计算机程序.操作系统需要处理如管理与配置内存.决定系统资源供需的优先次序.控制输入设备与输出设备.操作网络与 ...

  9. Mac OS X下安装Java 7及配置Eclipse JDK

    2019独角兽企业重金招聘Python工程师标准>>> 下载mac版专用的jdk1.7,地址如下:http://www.oracle.com/technetwork/java/jav ...

最新文章

  1. LeetCode实战:搜索二维矩阵
  2. java jstack dump 线程 介绍 解释
  3. 报名丨图神经网络前沿学术研讨会:清北高校vs企业,9位学者联袂分享
  4. Redis - 事务
  5. spring 的配置 beanpropertyname属性
  6. 无线网状网、Zigbee、RFID三种技术分析
  7. 图数据库在CMDB领域的应用
  8. MySQL_JDBC_数据库连接池
  9. matlab散点图与colorbar,MATLAB如何为图像做colorbar
  10. matlab实现图像处理教程,Matlab图像处理入门教程(菜鸟级)
  11. 古筝d调变降e调怎么办_为什么古筝总要调音、还总调不好?
  12. 极米newz6x和newz8x区别 极米newz6x和newz8x哪款好 哪个性价比高
  13. 全国/全世界城市Json数据大全
  14. 【转载】R6034错误,C Runtime Error
  15. Redis中的ttl命令用法解析
  16. ConcurrentHashMap的扩容方法transfer源码详解
  17. oracle like通配符区分大小写
  18. C++模板类的运算符重载
  19. NetBios网络基础及编程
  20. 设计一个学校在册人员类(Person)。数据成员包括身份证号(IdPerson)、姓名(Name)、性别(Sex)、生日(Birth-day)和家庭住址(HomeAddress)。成员函数包括人员信息

热门文章

  1. 自己动手写编译器、链接器目录结构
  2. Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---模板方法模式之CoffeineBeverageWithHook[转]...
  3. [Vue.js]实战 -- 电商项目(七)
  4. Opencv ORC——文字定位与切割
  5. Modularity(模块化-CommonJS规范)
  6. linux关闭计算机的命令是,关闭Linux计算机的正确命令和方法
  7. html iframe 不限制大小_渗透技术再升级:如何利用HTML注入泄漏用户数据
  8. PostGIS mysql_fdw安装(Linux)
  9. xadmin 显示外键字段
  10. HTML如何实现斜体字