com.github.jamesnetherton.zulip.client.http.commons.ZulipCommonsHttpClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zulip-java-client Show documentation
Show all versions of zulip-java-client Show documentation
Java client for the Zulip REST API
The newest version!
package com.github.jamesnetherton.zulip.client.http.commons;
import com.github.jamesnetherton.zulip.client.api.core.ZulipApiResponse;
import com.github.jamesnetherton.zulip.client.exception.ZulipClientException;
import com.github.jamesnetherton.zulip.client.exception.ZulipRateLimitExceededException;
import com.github.jamesnetherton.zulip.client.http.ZulipConfiguration;
import com.github.jamesnetherton.zulip.client.http.ZulipHttpClient;
import com.github.jamesnetherton.zulip.client.util.JsonUtils;
import com.github.jamesnetherton.zulip.client.util.ZulipUrlUtils;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
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.ContentType;
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.
*/
class ZulipCommonsHttpClient implements ZulipHttpClient {
private static final Logger LOG = Logger.getLogger(ZulipCommonsHttpClient.class.getName());
private final ZulipConfiguration configuration;
private CloseableHttpClient client;
private HttpClientContext context;
/**
* Constructs a {@link ZulipCommonsHttpClient}.
*
* @param configuration The configuration for the HTTP client
* @throws ZulipClientException if configuration of the HTTP client fails
*/
public ZulipCommonsHttpClient(ZulipConfiguration configuration) throws ZulipClientException {
if (configuration == null) {
throw new IllegalArgumentException("ZulipConfiguration cannot be null");
}
this.configuration = configuration;
configure();
}
/**
* Configures the HTTP client.
*
* @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().toCharArray());
BasicCredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(new AuthScope(targetHost), credentials);
HttpClientBuilder builder = HttpClientBuilder.create()
.setDefaultCredentialsProvider(provider);
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.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.toCharArray()));
}
}
context = HttpClientContext.create();
context.setCredentialsProvider(provider);
context.setAuthCache(authCache);
if (configuration.isInsecure()) {
try {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, TrustSelfSignedStrategy.INSTANCE)
.build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
NoopHostnameVerifier.INSTANCE);
HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder
.create()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
builder.setConnectionManager(connectionManager);
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
throw new ZulipClientException(e);
}
}
this.client = builder.useSystemProperties().build();
}
@Override
public T get(String path, Map parameters, Class responseAs)
throws ZulipClientException {
return doRequest(new HttpGet(getRequestUri(path, parameters)), responseAs);
}
@Override
public T delete(String path, Map parameters, Class responseAs)
throws ZulipClientException {
return doRequest(new HttpDelete(getRequestUri(path, parameters)), responseAs);
}
@Override
public T patch(String path, Map parameters, Class responseAs)
throws ZulipClientException {
HttpPatch request = new HttpPatch(getRequestUri(path, null));
configureFormEntity(request, parameters);
return doRequest(request, responseAs);
}
@Override
public T post(String path, Map parameters, Class responseAs)
throws ZulipClientException {
HttpPost request = new HttpPost(getRequestUri(path, null));
configureFormEntity(request, parameters);
return doRequest(request, responseAs);
}
@Override
public T upload(String path, File file, Class responseAs) throws ZulipClientException {
ContentType contentType;
try {
contentType = ContentType.create(Files.probeContentType(file.toPath()));
} catch (IOException e) {
contentType = ContentType.DEFAULT_BINARY;
}
HttpEntity entity = MultipartEntityBuilder.create()
.addBinaryBody("files", file, contentType, file.getName())
.build();
HttpPost httpPost = new HttpPost(getRequestUri(path, null));
httpPost.setEntity(entity);
return doRequest(httpPost, responseAs);
}
private T doRequest(ClassicHttpRequest request, Class responseAs)
throws ZulipClientException {
try {
ResponseHolder response = client.execute(request, context, new HttpClientResponseHandler() {
@Override
public ResponseHolder handleResponse(ClassicHttpResponse response) throws IOException {
Header header = response.getFirstHeader("x-ratelimit-reset");
int status = response.getCode();
if ((status >= 200 && status < 300) || (status == 400)) {
HttpEntity entity = response.getEntity();
if (entity != null) {
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);
}
} else if (status == 429) {
return new ResponseHolder(null, status, header);
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
}
});
if (response.getStatusCode() == 429) {
ZulipRateLimitExceededException rateLimitExceededException = new ZulipRateLimitExceededException(
response.getRateLimitReset());
throw new ZulipClientException(rateLimitExceededException);
}
ZulipApiResponse zulipApiResponse = response.getResponse();
if (zulipApiResponse == null) {
throw new ZulipClientException("Response was empty");
}
if (!zulipApiResponse.isSuccess() && !zulipApiResponse.isPartiallyCompleted()) {
throw new ZulipClientException(zulipApiResponse.getResponseMessage(), zulipApiResponse.getResponseCode());
}
return responseAs.cast(zulipApiResponse);
} catch (IOException e) {
throw new ZulipClientException(e);
}
}
private URI getRequestUri(String path, Map parameters) throws ZulipClientException {
URL zulipUrl = configuration.getZulipUrl();
URIBuilder builder = new URIBuilder()
.setScheme(zulipUrl.getProtocol())
.setHost(zulipUrl.getHost())
.setPort(zulipUrl.getPort())
.setPath(ZulipUrlUtils.API_BASE_PATH + "/" + path);
if (parameters != null) {
for (Map.Entry entry : parameters.entrySet()) {
if (entry.getValue() != null) {
builder.addParameter(entry.getKey(), entry.getValue().toString());
}
}
}
try {
return builder.build();
} catch (URISyntaxException e) {
throw new ZulipClientException(e);
}
}
private void configureFormEntity(ClassicHttpRequest request, Map parameters) {
List urlParameters = new ArrayList<>();
if (!parameters.isEmpty()) {
for (Map.Entry entry : parameters.entrySet()) {
if (entry.getValue() != null) {
urlParameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
}
}
request.setEntity(new UrlEncodedFormEntity(urlParameters, StandardCharsets.UTF_8));
}
}
@Override
public void close() {
if (this.client != null) {
try {
this.client.close();
} catch (IOException e) {
LOG.warning(e.getMessage());
}
}
}
private static final class ResponseHolder {
private final ZulipApiResponse response;
private final int statusCode;
private final Header header;
private ResponseHolder(ZulipApiResponse response, int statusCode, Header header) {
this.response = response;
this.statusCode = statusCode;
this.header = header;
}
public ZulipApiResponse getResponse() {
return response;
}
public int getStatusCode() {
return statusCode;
}
public long getRateLimitReset() {
long reset = 0;
if (header != null) {
String headerValue = header.getValue();
if (headerValue != null) {
try {
reset = Long.parseLong(headerValue);
} catch (NumberFormatException e) {
// Ignored
}
}
}
return reset;
}
}
}