Skip to content

Commit e890aff

Browse files
kkewweiok2c
authored andcommitted
HTTPCORE-761: support ExtendedSocketOption (#473)
1 parent 9be6b1c commit e890aff

File tree

10 files changed

+430
-6
lines changed

10 files changed

+430
-6
lines changed

httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpRequester.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.apache.hc.core5.io.CloseMode;
7777
import org.apache.hc.core5.io.Closer;
7878
import org.apache.hc.core5.io.ModalCloseable;
79+
import org.apache.hc.core5.io.SocketSupport;
7980
import org.apache.hc.core5.net.URIAuthority;
8081
import org.apache.hc.core5.pool.ConnPoolControl;
8182
import org.apache.hc.core5.pool.ManagedConnPool;
@@ -249,6 +250,15 @@ private HttpClientConnection createConnection(final Socket sock, final HttpHost
249250
if (socketConfig.getSndBufSize() > 0) {
250251
sock.setSendBufferSize(socketConfig.getSndBufSize());
251252
}
253+
if (this.socketConfig.getTcpKeepIdle() > 0) {
254+
SocketSupport.setOption(sock, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle());
255+
}
256+
if (this.socketConfig.getTcpKeepInterval() > 0) {
257+
SocketSupport.setOption(sock, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval());
258+
}
259+
if (this.socketConfig.getTcpKeepCount() > 0) {
260+
SocketSupport.setOption(sock, SocketSupport.TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount());
261+
}
252262
final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
253263
if (linger >= 0) {
254264
sock.setSoLinger(true, linger);

httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.apache.hc.core5.io.CloseMode;
5757
import org.apache.hc.core5.io.Closer;
5858
import org.apache.hc.core5.io.ModalCloseable;
59+
import org.apache.hc.core5.io.SocketSupport;
5960
import org.apache.hc.core5.util.Args;
6061
import org.apache.hc.core5.util.TimeValue;
6162
import org.apache.hc.core5.util.Timeout;
@@ -145,6 +146,15 @@ public void start() throws IOException {
145146
if (this.socketConfig.getRcvBufSize() > 0) {
146147
this.serverSocket.setReceiveBufferSize(this.socketConfig.getRcvBufSize());
147148
}
149+
if (this.socketConfig.getTcpKeepIdle() > 0) {
150+
SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle());
151+
}
152+
if (this.socketConfig.getTcpKeepInterval() > 0) {
153+
SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval());
154+
}
155+
if (this.socketConfig.getTcpKeepCount() > 0) {
156+
SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount());
157+
}
148158
if (this.sslSetupHandler != null && this.serverSocket instanceof SSLServerSocket) {
149159
final SSLServerSocket sslServerSocket = (SSLServerSocket) this.serverSocket;
150160
final SSLParameters sslParameters = sslServerSocket.getSSLParameters();

httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/RequestListener.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.apache.hc.core5.http.io.HttpServerConnection;
4646
import org.apache.hc.core5.http.io.SocketConfig;
4747
import org.apache.hc.core5.io.Closer;
48+
import org.apache.hc.core5.io.SocketSupport;
4849

4950
class RequestListener implements Runnable {
5051

@@ -91,6 +92,15 @@ private HttpServerConnection createConnection(final Socket socket) throws IOExce
9192
if (this.socketConfig.getSoLinger().toSeconds() >= 0) {
9293
socket.setSoLinger(true, this.socketConfig.getSoLinger().toSecondsIntBound());
9394
}
95+
if (this.socketConfig.getTcpKeepIdle() > 0) {
96+
SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPIDLE, this.socketConfig.getTcpKeepIdle());
97+
}
98+
if (this.socketConfig.getTcpKeepInterval() > 0) {
99+
SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPINTERVAL, this.socketConfig.getTcpKeepInterval());
100+
}
101+
if (this.socketConfig.getTcpKeepCount() > 0) {
102+
SocketSupport.setOption(this.serverSocket, SocketSupport.TCP_KEEPCOUNT, this.socketConfig.getTcpKeepCount());
103+
}
94104
if (!(socket instanceof SSLSocket) && sslSocketFactory != null) {
95105
final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, null, -1, false);
96106
sslSocket.setUseClientMode(false);

httpcore5/src/main/java/org/apache/hc/core5/http/io/SocketConfig.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ public class SocketConfig {
5656
private final int sndBufSize;
5757
private final int rcvBufSize;
5858
private final int backlogSize;
59+
private final int tcpKeepIdle;
60+
private final int tcpKeepInterval;
61+
private final int tcpKeepCount;
5962
private final SocketAddress socksProxyAddress;
6063

6164
SocketConfig(
@@ -67,6 +70,9 @@ public class SocketConfig {
6770
final int sndBufSize,
6871
final int rcvBufSize,
6972
final int backlogSize,
73+
final int tcpKeepIdle,
74+
final int tcpKeepInterval,
75+
final int tcpKeepCount,
7076
final SocketAddress socksProxyAddress) {
7177
super();
7278
this.soTimeout = soTimeout;
@@ -77,6 +83,9 @@ public class SocketConfig {
7783
this.sndBufSize = sndBufSize;
7884
this.rcvBufSize = rcvBufSize;
7985
this.backlogSize = backlogSize;
86+
this.tcpKeepIdle = tcpKeepIdle;
87+
this.tcpKeepInterval = tcpKeepInterval;
88+
this.tcpKeepCount = tcpKeepCount;
8089
this.socksProxyAddress = socksProxyAddress;
8190
}
8291

@@ -139,6 +148,30 @@ public int getBacklogSize() {
139148
return backlogSize;
140149
}
141150

151+
/**
152+
* @see Builder#setTcpKeepIdle(int)
153+
* @since 5.3
154+
*/
155+
public int getTcpKeepIdle() {
156+
return this.tcpKeepIdle;
157+
}
158+
159+
/**
160+
* @see Builder#setTcpKeepInterval(int)
161+
* @since 5.3
162+
*/
163+
public int getTcpKeepInterval() {
164+
return this.tcpKeepInterval;
165+
}
166+
167+
/**
168+
* @see Builder#setTcpKeepCount(int)
169+
* @since 5.3
170+
*/
171+
public int getTcpKeepCount() {
172+
return this.tcpKeepCount;
173+
}
174+
142175
/**
143176
* @see Builder#setSocksProxyAddress(SocketAddress)
144177
*/
@@ -157,6 +190,9 @@ public String toString() {
157190
.append(", sndBufSize=").append(this.sndBufSize)
158191
.append(", rcvBufSize=").append(this.rcvBufSize)
159192
.append(", backlogSize=").append(this.backlogSize)
193+
.append(", tcpKeepIdle=").append(this.tcpKeepIdle)
194+
.append(", tcpKeepInterval=").append(this.tcpKeepInterval)
195+
.append(", tcpKeepCount=").append(this.tcpKeepCount)
160196
.append(", socksProxyAddress=").append(this.socksProxyAddress)
161197
.append("]");
162198
return builder.toString();
@@ -177,6 +213,9 @@ public static SocketConfig.Builder copy(final SocketConfig config) {
177213
.setSndBufSize(config.getSndBufSize())
178214
.setRcvBufSize(config.getRcvBufSize())
179215
.setBacklogSize(config.getBacklogSize())
216+
.setTcpKeepIdle(config.getTcpKeepIdle())
217+
.setTcpKeepInterval(config.getTcpKeepInterval())
218+
.setTcpKeepCount(config.getTcpKeepCount())
180219
.setSocksProxyAddress(config.getSocksProxyAddress());
181220
}
182221

@@ -190,6 +229,9 @@ public static class Builder {
190229
private int sndBufSize;
191230
private int rcvBufSize;
192231
private int backlogSize;
232+
private int tcpKeepIdle;
233+
private int tcpKeepInterval;
234+
private int tcpKeepCount;
193235
private SocketAddress socksProxyAddress;
194236

195237
Builder() {
@@ -201,6 +243,9 @@ public static class Builder {
201243
this.sndBufSize = 0;
202244
this.rcvBufSize = 0;
203245
this.backlogSize = 0;
246+
this.tcpKeepIdle = -1;
247+
this.tcpKeepInterval = -1;
248+
this.tcpKeepCount = -1;
204249
this.socksProxyAddress = null;
205250
}
206251

@@ -340,6 +385,46 @@ public Builder setBacklogSize(final int backlogSize) {
340385
return this;
341386
}
342387

388+
/**
389+
* Determines the time (in seconds) the connection needs to remain idle before TCP starts
390+
* sending keepalive probes.
391+
* <p>
392+
* Default: {@code -1} (system default)
393+
* </p>
394+
* @return the time (in seconds) the connection needs to remain idle before TCP starts
395+
* @since 5.3
396+
*/
397+
public Builder setTcpKeepIdle(final int tcpKeepIdle) {
398+
this.tcpKeepIdle = tcpKeepIdle;
399+
return this;
400+
}
401+
402+
/**
403+
* Determines the time (in seconds) between individual keepalive probes.
404+
* <p>
405+
* Default: {@code -1} (system default)
406+
* </p>
407+
* @return the time (in seconds) between individual keepalive probes.
408+
* @since 5.3
409+
*/
410+
public Builder setTcpKeepInterval(final int tcpKeepInterval) {
411+
this.tcpKeepInterval = tcpKeepInterval;
412+
return this;
413+
}
414+
415+
/**
416+
* Determines the maximum number of keepalive probes TCP should send before dropping the connection.
417+
* <p>
418+
* Default: {@code -1} (system default)
419+
* </p>
420+
* @return the maximum number of keepalive probes TCP should send before dropping the connection.
421+
* @since 5.3
422+
*/
423+
public Builder setTcpKeepCount(final int tcpKeepCount) {
424+
this.tcpKeepCount = tcpKeepCount;
425+
return this;
426+
}
427+
343428
/**
344429
* The address of the SOCKS proxy to use.
345430
*/
@@ -354,6 +439,7 @@ public SocketConfig build() {
354439
soReuseAddress,
355440
soLinger != null ? soLinger : TimeValue.NEG_ONE_SECOND,
356441
soKeepAlive, tcpNoDelay, sndBufSize, rcvBufSize, backlogSize,
442+
tcpKeepIdle, tcpKeepInterval, tcpKeepCount,
357443
socksProxyAddress);
358444
}
359445

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
28+
package org.apache.hc.core5.io;
29+
30+
import java.io.IOException;
31+
import java.lang.reflect.Field;
32+
import java.lang.reflect.Method;
33+
import java.net.SocketOption;
34+
35+
import org.apache.hc.core5.annotation.Internal;
36+
37+
/**
38+
* @since 5.3
39+
*/
40+
@Internal
41+
public class SocketSupport {
42+
43+
public static final String TCP_KEEPIDLE = "TCP_KEEPIDLE";
44+
public static final String TCP_KEEPINTERVAL = "TCP_KEEPINTERVAL";
45+
public static final String TCP_KEEPCOUNT = "TCP_KEEPCOUNT";
46+
47+
@SuppressWarnings("unchecked")
48+
public static <T> SocketOption<T> getExtendedSocketOptionOrNull(final String fieldName) {
49+
try {
50+
final Class<?> extendedSocketOptionsClass = Class.forName("jdk.net.ExtendedSocketOptions");
51+
final Field field = extendedSocketOptionsClass.getField(fieldName);
52+
return (SocketOption<T>) field.get(null);
53+
} catch (final Exception ignore) {
54+
return null;
55+
}
56+
}
57+
58+
/**
59+
* object can be ServerSocket or Socket
60+
*/
61+
public static <T> void setOption(final T object, final String fieldName, final T value) throws IOException {
62+
try {
63+
final Class<?> serverSocketClass = object.getClass();
64+
final Method setOptionMethod = serverSocketClass.getMethod("setOption", SocketOption.class, Object.class);
65+
final SocketOption<Integer> socketOption = getExtendedSocketOptionOrNull(fieldName);
66+
if (socketOption == null) {
67+
throw new UnsupportedOperationException("Extended socket option not supported: " + fieldName);
68+
}
69+
setOptionMethod.invoke(object, socketOption, value);
70+
} catch (final UnsupportedOperationException e) {
71+
throw e;
72+
} catch (final Exception ex) {
73+
throw new IOException("Failure setting extended socket option", ex);
74+
}
75+
}
76+
77+
}

0 commit comments

Comments
 (0)