Skip to content

Commit 2a8770f

Browse files
Migrate to httpclient5 (#1030)
Co-authored-by: strangelookingnerd <[email protected]>
1 parent be6a66d commit 2a8770f

18 files changed

+379
-393
lines changed

pom.xml

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262

6363
<dependencies>
6464
<!-- required plugins -->
65+
<dependency>
66+
<groupId>io.jenkins.plugins</groupId>
67+
<artifactId>apache-httpcomponents-client-5-api</artifactId>
68+
</dependency>
6569
<dependency>
6670
<groupId>io.jenkins.plugins</groupId>
6771
<artifactId>commons-lang3-api</artifactId>
@@ -90,10 +94,6 @@
9094
<groupId>org.jenkins-ci.plugins</groupId>
9195
<artifactId>jackson2-api</artifactId>
9296
</dependency>
93-
<dependency>
94-
<groupId>org.jenkins-ci.plugins</groupId>
95-
<artifactId>apache-httpcomponents-client-4-api</artifactId>
96-
</dependency>
9797
<dependency>
9898
<groupId>org.jenkins-ci.plugins</groupId>
9999
<artifactId>credentials</artifactId>

src/main/java/jenkins/plugins/slack/HttpClient.java

+29-20
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22

33
import hudson.ProxyConfiguration;
44
import hudson.util.Secret;
5-
import org.apache.http.HttpHost;
6-
import org.apache.http.auth.AuthScope;
7-
import org.apache.http.auth.Credentials;
8-
import org.apache.http.auth.NTCredentials;
9-
import org.apache.http.auth.UsernamePasswordCredentials;
10-
import org.apache.http.client.CredentialsProvider;
11-
import org.apache.http.client.config.RequestConfig;
12-
import org.apache.http.conn.routing.HttpRoutePlanner;
13-
import org.apache.http.impl.client.BasicCredentialsProvider;
14-
import org.apache.http.impl.client.CloseableHttpClient;
15-
import org.apache.http.impl.client.HttpClientBuilder;
16-
import org.apache.http.impl.client.HttpClients;
5+
import org.apache.hc.client5.http.auth.AuthScope;
6+
import org.apache.hc.client5.http.auth.Credentials;
7+
import org.apache.hc.client5.http.auth.NTCredentials;
8+
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
9+
import org.apache.hc.client5.http.config.ConnectionConfig;
10+
import org.apache.hc.client5.http.config.RequestConfig;
11+
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
12+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
13+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
14+
import org.apache.hc.client5.http.impl.classic.HttpClients;
15+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
16+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
17+
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
18+
import org.apache.hc.core5.http.HttpHost;
19+
import org.apache.hc.core5.util.Timeout;
1720
import org.kohsuke.accmod.Restricted;
1821
import org.kohsuke.accmod.restrictions.NoExternalUse;
1922

@@ -23,16 +26,22 @@ public class HttpClient {
2326
public static HttpClientBuilder getCloseableHttpClientBuilder(ProxyConfiguration proxy) {
2427
int timeoutInSeconds = 60;
2528

26-
RequestConfig config = RequestConfig.custom()
27-
.setConnectTimeout(timeoutInSeconds * 1000)
28-
.setConnectionRequestTimeout(timeoutInSeconds * 1000)
29-
.setSocketTimeout(timeoutInSeconds * 1000).build();
29+
ConnectionConfig connectionConfig = ConnectionConfig.custom()
30+
.setConnectTimeout(Timeout.ofSeconds(timeoutInSeconds))
31+
.setSocketTimeout(Timeout.ofSeconds(timeoutInSeconds)).build();
32+
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
33+
.setDefaultConnectionConfig(connectionConfig)
34+
.build();
35+
36+
RequestConfig requestConfig = RequestConfig.custom()
37+
.setConnectionRequestTimeout(Timeout.ofSeconds(timeoutInSeconds)).build();
3038

3139
final HttpClientBuilder clientBuilder = HttpClients
3240
.custom()
3341
.useSystemProperties()
34-
.setDefaultRequestConfig(config);
35-
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
42+
.setConnectionManager(connectionManager)
43+
.setDefaultRequestConfig(requestConfig);
44+
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
3645
clientBuilder.setDefaultCredentialsProvider(credentialsProvider);
3746

3847
if (proxy != null) {
@@ -61,9 +70,9 @@ private static Credentials createCredentials(String userName, String password) {
6170
if (userName.indexOf('\\') >= 0){
6271
final String domain = userName.substring(0, userName.indexOf('\\'));
6372
final String user = userName.substring(userName.indexOf('\\') + 1);
64-
return new NTCredentials(user, password, "", domain);
73+
return new NTCredentials(user, password.toCharArray(), "", domain);
6574
} else {
66-
return new UsernamePasswordCredentials(userName, password);
75+
return new UsernamePasswordCredentials(userName, password.toCharArray());
6776
}
6877
}
6978
}

src/main/java/jenkins/plugins/slack/NoProxyHostCheckerRoutePlanner.java

+13-11
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
import hudson.ProxyConfiguration;
44
import java.util.regex.Pattern;
5-
import org.apache.http.HttpHost;
6-
import org.apache.http.HttpRequest;
7-
import org.apache.http.conn.routing.HttpRoute;
8-
import org.apache.http.conn.routing.HttpRoutePlanner;
9-
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
10-
import org.apache.http.impl.conn.DefaultRoutePlanner;
11-
import org.apache.http.impl.conn.DefaultSchemePortResolver;
12-
import org.apache.http.protocol.HttpContext;
5+
import org.apache.hc.client5.http.HttpRoute;
6+
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
7+
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
8+
import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
9+
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
10+
import org.apache.hc.core5.http.HttpException;
11+
import org.apache.hc.core5.http.HttpHost;
12+
import org.apache.hc.core5.http.protocol.HttpContext;
13+
1314

1415
public class NoProxyHostCheckerRoutePlanner implements HttpRoutePlanner {
1516

@@ -27,11 +28,12 @@ public void setProxy(HttpHost host){
2728
defaultProxyRoutePlanner = new DefaultProxyRoutePlanner(host);
2829
}
2930

30-
public HttpRoute determineRoute(HttpHost target, HttpRequest request, HttpContext context) throws org.apache.http.HttpException {
31+
@Override
32+
public HttpRoute determineRoute(HttpHost target, HttpContext context) throws HttpException {
3133
final String targetHostUri = target.toURI();
3234
if(isNoProxyHost(targetHostUri))
33-
return defaultRoutePlanner.determineRoute(target,request,context);
34-
return defaultProxyRoutePlanner.determineRoute(target,request,context);
35+
return defaultRoutePlanner.determineRoute(target, context);
36+
return defaultProxyRoutePlanner.determineRoute(target, context);
3537
}
3638

3739
private boolean isNoProxyHost(String host) {

src/main/java/jenkins/plugins/slack/StandardSlackService.java

+8-10
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@
2828
import net.sf.json.JSONArray;
2929
import net.sf.json.JSONObject;
3030
import org.apache.commons.lang3.StringUtils;
31-
import org.apache.http.HttpEntity;
32-
import org.apache.http.HttpStatus;
33-
import org.apache.http.client.methods.CloseableHttpResponse;
34-
import org.apache.http.client.methods.HttpPost;
35-
import org.apache.http.entity.StringEntity;
36-
import org.apache.http.impl.client.CloseableHttpClient;
37-
import org.apache.http.util.EntityUtils;
31+
import org.apache.hc.client5.http.classic.methods.HttpPost;
32+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
33+
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
34+
import org.apache.hc.core5.http.HttpEntity;
35+
import org.apache.hc.core5.http.HttpStatus;
36+
import org.apache.hc.core5.http.io.entity.EntityUtils;
37+
import org.apache.hc.core5.http.io.entity.StringEntity;
3838
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
3939

4040
public class StandardSlackService implements SlackService {
@@ -214,7 +214,7 @@ boolean postToSlack(String apiEndpoint, JSONObject body) {
214214
post.setEntity(new StringEntity(body.toString(), StandardCharsets.UTF_8));
215215

216216
try (CloseableHttpResponse response = client.execute(post)) {
217-
int responseCode = response.getStatusLine().getStatusCode();
217+
int responseCode = response.getCode();
218218
HttpEntity entity = response.getEntity();
219219
if (botUser && entity != null) {
220220
responseString = EntityUtils.toString(entity);
@@ -237,8 +237,6 @@ boolean postToSlack(String apiEndpoint, JSONObject body) {
237237
} catch (Exception e) {
238238
logger.log(Level.WARNING, "Error posting to Slack", e);
239239
result = false;
240-
} finally {
241-
post.releaseConnection();
242240
}
243241
} catch (IOException e) {
244242
logger.log(Level.WARNING, "Error closing HttpClient", e);

src/main/java/jenkins/plugins/slack/cache/SlackChannelIdCache.java

+42-36
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.github.benmanes.caffeine.cache.LoadingCache;
55
import hudson.AbortException;
66
import java.io.IOException;
7+
import java.net.URISyntaxException;
78
import java.time.Duration;
89
import java.util.HashMap;
910
import java.util.Map;
@@ -13,17 +14,20 @@
1314
import java.util.logging.Logger;
1415
import jenkins.model.Jenkins;
1516
import jenkins.plugins.slack.HttpClient;
16-
import org.apache.http.Header;
17-
import org.apache.http.HttpEntity;
18-
import org.apache.http.HttpResponse;
19-
import org.apache.http.HttpStatus;
20-
import org.apache.http.client.ResponseHandler;
21-
import org.apache.http.client.ServiceUnavailableRetryStrategy;
22-
import org.apache.http.client.methods.RequestBuilder;
23-
import org.apache.http.impl.client.CloseableHttpClient;
24-
import org.apache.http.impl.client.HttpClientBuilder;
25-
import org.apache.http.protocol.HttpContext;
26-
import org.apache.http.util.EntityUtils;
17+
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
18+
import org.apache.hc.client5.http.fluent.Request;
19+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
20+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
21+
import org.apache.hc.core5.http.Header;
22+
import org.apache.hc.core5.http.HttpEntity;
23+
import org.apache.hc.core5.http.HttpRequest;
24+
import org.apache.hc.core5.http.HttpResponse;
25+
import org.apache.hc.core5.http.HttpStatus;
26+
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
27+
import org.apache.hc.core5.http.io.entity.EntityUtils;
28+
import org.apache.hc.core5.http.protocol.HttpContext;
29+
import org.apache.hc.core5.net.URIBuilder;
30+
import org.apache.hc.core5.util.TimeValue;
2731
import org.json.JSONArray;
2832
import org.json.JSONObject;
2933

@@ -41,15 +45,14 @@ public class SlackChannelIdCache {
4145

4246
private static Map<String, String> populateCache(String token) {
4347
HttpClientBuilder closeableHttpClientBuilder = HttpClient.getCloseableHttpClientBuilder(Jenkins.get().getProxy())
44-
.setRetryHandler((exception, executionCount, context) -> executionCount <= MAX_RETRIES)
45-
.setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryStrategy() {
48+
.setRetryStrategy(new HttpRequestRetryStrategy() {
4649

47-
long retryInterval;
50+
private long retryInterval;
4851

4952
@Override
5053
public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {
5154
boolean shouldRetry = executionCount <= MAX_RETRIES &&
52-
response.getStatusLine().getStatusCode() == HttpStatus.SC_TOO_MANY_REQUESTS;
55+
response.getCode() == HttpStatus.SC_TOO_MANY_REQUESTS;
5356
if (shouldRetry) {
5457
Header firstHeader = response.getFirstHeader("Retry-After");
5558
if (firstHeader != null) {
@@ -61,13 +64,18 @@ public boolean retryRequest(HttpResponse response, int executionCount, HttpConte
6164
}
6265

6366
@Override
64-
public long getRetryInterval() {
65-
return retryInterval;
67+
public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) {
68+
return false;
69+
}
70+
71+
@Override
72+
public TimeValue getRetryInterval(HttpResponse response, int execCount, HttpContext context) {
73+
return TimeValue.ofSeconds(retryInterval);
6674
}
6775
});
6876
try (CloseableHttpClient client = closeableHttpClientBuilder.build()) {
6977
return convertChannelNameToId(client, token, new HashMap<>(), null);
70-
} catch (IOException e) {
78+
} catch (IOException | URISyntaxException e) {
7179
throw new RuntimeException(e);
7280
}
7381
}
@@ -110,24 +118,23 @@ private static String cleanChannelName(String channelName) {
110118
}
111119

112120

113-
private static Map<String, String> convertChannelNameToId(CloseableHttpClient client, String token, Map<String, String> channels, String cursor) throws IOException {
121+
private static Map<String, String> convertChannelNameToId(CloseableHttpClient client, String token, Map<String, String> channels, String cursor) throws IOException, URISyntaxException {
114122
convertPublicChannelNameToId(client, token, channels, cursor);
115123
convertPrivateChannelNameToId(client, token, channels, cursor);
116124
return channels;
117125
}
118126

119-
private static Map<String, String> convertPublicChannelNameToId(CloseableHttpClient client, String token, Map<String, String> channels, String cursor) throws IOException {
120-
RequestBuilder requestBuilder = RequestBuilder.get("https://slack.com/api/conversations.list")
121-
.addHeader("Authorization", "Bearer " + token)
127+
private static Map<String, String> convertPublicChannelNameToId(CloseableHttpClient client, String token, Map<String, String> channels, String cursor) throws IOException, URISyntaxException {
128+
URIBuilder uriBuilder = new URIBuilder("https://slack.com/api/conversations.list")
122129
.addParameter("exclude_archived", "true")
123130
.addParameter("types", "public_channel")
124131
.addParameter("limit", "999");
125-
126132
if (cursor != null) {
127-
requestBuilder.addParameter("cursor", cursor);
133+
uriBuilder.addParameter("cursor", cursor);
128134
}
129-
ResponseHandler<JSONObject> standardResponseHandler = getStandardResponseHandler();
130-
JSONObject result = client.execute(requestBuilder.build(), standardResponseHandler);
135+
Request requestBuilder = Request.get(uriBuilder.build())
136+
.addHeader("Authorization", "Bearer " + token);
137+
JSONObject result = requestBuilder.execute(client).handleResponse(getStandardResponseHandler());
131138

132139
if (!result.getBoolean("ok")) {
133140
logger.warning("Couldn't convert channel name to ID in Slack: " + result);
@@ -152,18 +159,17 @@ private static Map<String, String> convertPublicChannelNameToId(CloseableHttpCli
152159
return channels;
153160
}
154161

155-
private static Map<String, String> convertPrivateChannelNameToId(CloseableHttpClient client, String token, Map<String, String> channels, String cursor) throws IOException {
156-
RequestBuilder requestBuilder = RequestBuilder.get("https://slack.com/api/conversations.list")
157-
.addHeader("Authorization", "Bearer " + token)
162+
private static Map<String, String> convertPrivateChannelNameToId(CloseableHttpClient client, String token, Map<String, String> channels, String cursor) throws IOException, URISyntaxException {
163+
URIBuilder uriBuilder = new URIBuilder("https://slack.com/api/conversations.list")
158164
.addParameter("exclude_archived", "true")
159165
.addParameter("types", "private_channel")
160166
.addParameter("limit", "999");
161-
162167
if (cursor != null) {
163-
requestBuilder.addParameter("cursor", cursor);
168+
uriBuilder.addParameter("cursor", cursor);
164169
}
165-
ResponseHandler<JSONObject> standardResponseHandler = getStandardResponseHandler();
166-
JSONObject result = client.execute(requestBuilder.build(), standardResponseHandler);
170+
Request requestBuilder = Request.get(uriBuilder.build())
171+
.addHeader("Authorization", "Bearer " + token);
172+
JSONObject result = requestBuilder.execute(client).handleResponse(getStandardResponseHandler());
167173

168174
if (!result.getBoolean("ok")) {
169175
logger.warning("Couldn't convert channel name to ID in Slack: " + result);
@@ -188,15 +194,15 @@ private static Map<String, String> convertPrivateChannelNameToId(CloseableHttpCl
188194
return channels;
189195
}
190196

191-
private static ResponseHandler<JSONObject> getStandardResponseHandler() {
197+
private static HttpClientResponseHandler<JSONObject> getStandardResponseHandler() {
192198
return response -> {
193-
int status = response.getStatusLine().getStatusCode();
199+
int status = response.getCode();
194200
if (status >= 200 && status < 300) {
195201
HttpEntity entity = response.getEntity();
196202
return entity != null ? new JSONObject(EntityUtils.toString(entity)) : null;
197203
} else {
198204
String errorMessage = UPLOAD_FAILED_TEMPLATE + status + " " + EntityUtils.toString(response.getEntity());
199-
throw new HttpStatusCodeException(response.getStatusLine().getStatusCode(), errorMessage);
205+
throw new HttpStatusCodeException(response.getCode(), errorMessage);
200206
}
201207
};
202208
}

0 commit comments

Comments
 (0)