Skip to content

Commit

Permalink
Upgrade to HttpClient 5.x
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesnetherton committed Aug 2, 2024
1 parent cd8dca6 commit 23e1225
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 69 deletions.
11 changes: 3 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<project.build.reportEncoding>UTF-8</project.build.reportEncoding>

<!-- Project dependency versions -->
<httpcomponents.version>4.5.14</httpcomponents.version>
<httpcomponents.version>5.3.1</httpcomponents.version>
<jackson.version>2.17.2</jackson.version>
<junit.jupiter.version>5.10.3</junit.jupiter.version>
<slf4j.version>2.0.13</slf4j.version>
Expand Down Expand Up @@ -94,13 +94,8 @@
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpcomponents.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${httpcomponents.version}</version>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,39 @@
import java.util.Map;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.auth.AuthCache;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPatch;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.auth.BasicScheme;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.ssl.SSLContextBuilder;

/**
* A {@link ZulipHttpClient} implementation that uses the Apache Commons HTTP Client.
Expand Down Expand Up @@ -86,31 +87,33 @@ public ZulipCommonsHttpClient(ZulipConfiguration configuration) throws ZulipClie
* @throws ZulipClientException if configuration fails
*/
public void configure() throws ZulipClientException {
URL zulipUrl = configuration.getZulipUrl();
HttpHost targetHost = new HttpHost(zulipUrl.getProtocol(), zulipUrl.getHost(), zulipUrl.getPort());

UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(configuration.getEmail(),
configuration.getApiKey());
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(AuthScope.ANY, credentials);
configuration.getApiKey().toCharArray());
BasicCredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(new AuthScope(targetHost), credentials);

HttpClientBuilder builder = HttpClientBuilder.create()
.setDefaultCredentialsProvider(provider);

URL zulipUrl = configuration.getZulipUrl();
HttpHost targetHost = new HttpHost(zulipUrl.getHost(), zulipUrl.getPort(), zulipUrl.getProtocol());
AuthCache authCache = new BasicAuthCache();
BasicScheme basicAuth = new BasicScheme();
basicAuth.initPreemptive(credentials);
authCache.put(targetHost, basicAuth);

URL proxyUrl = configuration.getProxyUrl();
if (proxyUrl != null) {
HttpHost proxyHost = new HttpHost(proxyUrl.getHost(), proxyUrl.getPort(), proxyUrl.getProtocol());
HttpHost proxyHost = new HttpHost(proxyUrl.getProtocol(), proxyUrl.getHost(), proxyUrl.getPort());
builder.setProxy(proxyHost);

String proxyUsername = configuration.getProxyUsername();
String proxyPassword = configuration.getProxyPassword();
if (proxyUsername != null && !proxyUsername.isEmpty() && proxyPassword != null && !proxyPassword.isEmpty()) {
provider.setCredentials(
new AuthScope(proxyHost.getHostName(), proxyHost.getPort()),
new UsernamePasswordCredentials(proxyUsername, proxyPassword));
new UsernamePasswordCredentials(proxyUsername, proxyPassword.toCharArray()));
}
}

Expand All @@ -120,18 +123,22 @@ public void configure() throws ZulipClientException {

if (configuration.isInsecure()) {
try {
SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLContext sslContext = sslContextBuilder.build();
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, TrustSelfSignedStrategy.INSTANCE)
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
NoopHostnameVerifier.INSTANCE);
builder.setSSLSocketFactory(sslConnectionSocketFactory);
HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder
.create()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
builder.setConnectionManager(connectionManager);
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
throw new ZulipClientException(e);
}
}

this.client = builder.build();
this.client = builder.useSystemProperties().build();
}

@Override
Expand Down Expand Up @@ -174,19 +181,24 @@ public <T extends ZulipApiResponse> T upload(String path, File file, Class<T> re
return doRequest(httpPost, responseAs);
}

private <T extends ZulipApiResponse> T doRequest(HttpUriRequest request, Class<T> responseAs) throws ZulipClientException {
private <T extends ZulipApiResponse> T doRequest(ClassicHttpRequest request, Class<T> responseAs)
throws ZulipClientException {
try {
ResponseHolder response = client.execute(request, new ResponseHandler<ResponseHolder>() {
ResponseHolder response = client.execute(request, context, new HttpClientResponseHandler<ResponseHolder>() {
@Override
public ResponseHolder handleResponse(HttpResponse response) throws IOException {
public ResponseHolder handleResponse(ClassicHttpResponse response) throws IOException {
Header header = response.getFirstHeader("x-ratelimit-reset");
int status = response.getStatusLine().getStatusCode();
int status = response.getCode();
if ((status >= 200 && status < 300) || (status == 400)) {
HttpEntity entity = response.getEntity();
if (entity != null) {
String json = EntityUtils.toString(entity);
ZulipApiResponse zulipApiResponse = JsonUtils.getMapper().readValue(json, responseAs);
return new ResponseHolder(zulipApiResponse, status, header);
try {
String json = EntityUtils.toString(entity);
ZulipApiResponse zulipApiResponse = JsonUtils.getMapper().readValue(json, responseAs);
return new ResponseHolder(zulipApiResponse, status, header);
} catch (ParseException e) {
return new ResponseHolder(new ZulipApiResponse(), status, header);
}
} else {
return new ResponseHolder(null, status, header);
}
Expand All @@ -196,7 +208,7 @@ public ResponseHolder handleResponse(HttpResponse response) throws IOException {
throw new ClientProtocolException("Unexpected response status: " + status);
}
}
}, context);
});

if (response.getStatusCode() == 429) {
ZulipRateLimitExceededException rateLimitExceededException = new ZulipRateLimitExceededException(
Expand Down Expand Up @@ -242,7 +254,7 @@ private URI getRequestUri(String path, Map<String, Object> parameters) throws Zu
}
}

private void configureFormEntity(HttpEntityEnclosingRequestBase request, Map<String, Object> parameters) {
private void configureFormEntity(ClassicHttpRequest request, Map<String, Object> parameters) {
List<NameValuePair> urlParameters = new ArrayList<>();
if (!parameters.isEmpty()) {
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ public void getUserByEmail() throws Exception {
.add(GetUserApiRequest.INCLUDE_CUSTOM_PROFILE_FIELDS, "true")
.get();

stubZulipResponse(GET, "/users/test@test.com", params, "getUser.json");
stubZulipResponse(GET, "/users/test%40test.com", params, "getUser.json");

User user = zulip.users().getUser("[email protected]")
.withClientGravatar(true)
Expand Down Expand Up @@ -442,7 +442,7 @@ public void updateUser() throws Exception {

@Test
public void userPresence() throws Exception {
stubZulipResponse(GET, "/users/test@test.com/presence", "getUserPresence.json");
stubZulipResponse(GET, "/users/test%40test.com/presence", "getUserPresence.json");

Map<String, UserPresenceDetail> presence = zulip.users().getUserPresence("[email protected]").execute();
assertEquals(2, presence.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class ZulipCommonsHttpClientTest extends ZulipApiTestBase {

@Test
public void errorResponseCodeThrowsZulipClientException() throws Exception {
server.stubFor(request("GET", urlPathEqualTo("/api/v1/"))
server.stubFor(request("GET", urlPathEqualTo("/api/v1"))
.willReturn(aResponse()
.withStatus(500)
.withBody((String) null)));
Expand All @@ -46,7 +46,7 @@ public void errorResponseCodeThrowsZulipClientException() throws Exception {
ZulipCommonsHttpClient client = new ZulipCommonsHttpClient(configuration);

assertThrows(ZulipClientException.class, () -> {
client.get("/", Collections.emptyMap(), ZulipApiResponse.class);
client.get("", Collections.emptyMap(), ZulipApiResponse.class);
});
}

Expand All @@ -64,7 +64,7 @@ public void invalidRateLimitReset() throws Exception {
ZulipCommonsHttpClient client = new ZulipCommonsHttpClient(configuration);

try {
client.get("/", Collections.emptyMap(), ZulipApiResponse.class);
client.get("", Collections.emptyMap(), ZulipApiResponse.class);
} catch (ZulipClientException e) {
ZulipRateLimitExceededException cause = (ZulipRateLimitExceededException) e.getCause();
assertEquals(0, cause.getReteLimitReset());
Expand Down Expand Up @@ -95,7 +95,7 @@ public void proxyServer() throws Exception {

@Test
public void proxyServerAuthentication() throws Exception {
CountDownLatch latch = new CountDownLatch(3);
CountDownLatch latch = new CountDownLatch(2);
FakeServer server = new FakeServer(latch, true);
server.start();

Expand Down Expand Up @@ -125,7 +125,7 @@ public void ignoredParameters() throws Exception {
ZulipConfiguration configuration = new ZulipConfiguration(zulipUrl, "[email protected]", "abc123");
ZulipCommonsHttpClient client = new ZulipCommonsHttpClient(configuration);

ZulipApiResponse response = client.get("/test", Collections.emptyMap(), ZulipApiResponse.class);
ZulipApiResponse response = client.get("test", Collections.emptyMap(), ZulipApiResponse.class);
List<String> ignoredParametersUnsupported = response.getIgnoredParametersUnsupported();
assertNotNull(ignoredParametersUnsupported);
assertEquals(2, ignoredParametersUnsupported.size());
Expand Down Expand Up @@ -156,6 +156,8 @@ void start() {
InputStreamReader isr = new InputStreamReader(accept.getInputStream());
BufferedReader reader = new BufferedReader(isr);
String line = reader.readLine();
latch.countDown();

while (!line.isEmpty()) {
if (secure && proxyAuthHeaderSent && line.startsWith("Proxy-Authorization")) {
latch.countDown();
Expand All @@ -181,7 +183,6 @@ void start() {
}

outputStream.write("HTTP/1.1 200 OK\r\n".getBytes(StandardCharsets.UTF_8));
outputStream.write("Content-Length: ".getBytes(StandardCharsets.UTF_8));
outputStream.write(
String.format("Content-Length: %d\r\n", response.length).getBytes(StandardCharsets.UTF_8));
outputStream.write("Content-Type: Application/json\r\n\r\n".getBytes(StandardCharsets.UTF_8));
Expand Down

0 comments on commit 23e1225

Please sign in to comment.