Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.alexdlaird.http.DefaultHttpClient Maven / Gradle / Ivy
/*
* Copyright (c) 2021-2024 Alex Laird
*
* SPDX-License-Identifier: MIT
*/
package com.github.alexdlaird.http;
import com.github.alexdlaird.util.StringUtils;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import static com.github.alexdlaird.util.StringUtils.isNotBlank;
import static java.util.Objects.nonNull;
/**
* A default client for executing JSON-based HTTP requests.
*
* Basic Usage
*
* final HttpClient httpClient = new DefaultHttpClient.Builder();
*
* final SomePOJORequest postPojo = new MyPOJO("id", "data");
* final Response<SomePOJOResponse> postResponse = httpClient.post("http://localhost/pojo",
* postPojo,
* SomePOJOResponse.class);
*
* final Response<SomePOJOResponse> getResponse = httpClient.get("http://localhost/pojo/id",
* SomePOJOResponse.class);
*
* final SomePOJORequest putPojo = new MyPOJO("updated-data");
* final Response<SomePOJOResponse> postResponse = httpClient.post("http://localhost/pojo/id",
* putPojo,
* SomePOJOResponse.class);
*
* final Response<Map> deleteResponse = httpClient.delete("http://localhost/pojo/id");
*
*/
public class DefaultHttpClient implements HttpClient {
private static final Logger LOGGER = Logger.getLogger(String.valueOf(DefaultHttpClient.class));
private final Gson gson;
private final String encoding;
private final String contentType;
private final int timeout;
private final int retryCount;
private DefaultHttpClient(final Builder builder) {
this.encoding = builder.encoding;
this.contentType = builder.contentType;
this.timeout = builder.timeout;
this.retryCount = builder.retryCount;
this.gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
}
@Override
public Response get(final String url,
final List parameters,
final Map additionalHeaders,
final Class clazz) {
try {
return execute(urlWithParameters(url, parameters), null, "GET",
additionalHeaders, clazz);
} catch (final UnsupportedEncodingException e) {
throw new HttpClientException("HTTP GET error", e);
}
}
@Override
public void get(final String url,
final List parameters,
final Map additionalHeaders,
final Path dest,
final int retries)
throws InterruptedException {
HttpURLConnection httpUrlConnection = null;
try {
httpUrlConnection = createHttpUrlConnection(urlWithParameters(url, parameters));
try (final InputStream inputStream = getInputStream(httpUrlConnection, null, "GET", additionalHeaders)) {
Files.copy(inputStream, dest, StandardCopyOption.REPLACE_EXISTING);
}
} catch (final Exception e) {
if (retries < retryCount) {
LOGGER.warning("GET failed, retrying in 0.5 seconds ...");
Thread.sleep(500);
get(url, parameters, additionalHeaders, dest, retries + 1);
} else {
String msg = "An unknown error occurred when downloading the file";
int statusCode = -1;
String errorResponse = null;
if (nonNull(httpUrlConnection)) {
try {
statusCode = httpUrlConnection.getResponseCode();
if (nonNull(httpUrlConnection.getErrorStream())) {
errorResponse = StringUtils.streamToString(httpUrlConnection.getErrorStream(),
Charset.forName(encoding));
}
msg = String.format("An error occurred when downloading the file (%s): %s",
httpUrlConnection.getResponseCode(),
errorResponse);
} catch (final IOException | NullPointerException ignored) {
}
}
throw new HttpClientException(msg, e, url, statusCode, errorResponse);
}
} finally {
if (nonNull(httpUrlConnection)) {
httpUrlConnection.disconnect();
}
}
}
@Override
public Response post(final String url,
final R request,
final List parameters,
final Map additionalHeaders,
final Class clazz) {
try {
return execute(urlWithParameters(url, parameters), convertRequestToString(request), "POST",
additionalHeaders, clazz);
} catch (final UnsupportedEncodingException e) {
throw new HttpClientException("HTTP POST error", e);
}
}
@Override
public Response put(final String url,
final R request,
final List parameters,
final Map additionalHeaders,
final Class clazz) {
try {
return execute(urlWithParameters(url, parameters), convertRequestToString(request), "PUT",
additionalHeaders, clazz);
} catch (final UnsupportedEncodingException e) {
throw new HttpClientException("HTTP PUT error", e);
}
}
@Override
public Response delete(final String url,
final List parameters,
final Map additionalHeaders,
final Class clazz) {
try {
return execute(urlWithParameters(url, parameters), null, "DELETE",
additionalHeaders, clazz);
} catch (final UnsupportedEncodingException e) {
throw new HttpClientException("HTTP DELETE error", e);
}
}
private void appendDefaultsToConnection(final HttpURLConnection httpUrlConnection,
final Map additionalHeaders) {
httpUrlConnection.setRequestProperty("Content-Type", contentType);
if (nonNull(additionalHeaders)) {
for (final Map.Entry entry : additionalHeaders.entrySet()) {
httpUrlConnection.setRequestProperty(entry.getKey(), entry.getValue());
}
}
}
private String convertRequestToString(final T request) {
if (nonNull(request)) {
return gson.toJson(request);
} else {
return null;
}
}
private T convertResponseFromString(final String response, final Class clazz) {
if (isNotBlank(response)) {
try {
return gson.fromJson(response, clazz);
} catch (final JsonSyntaxException e) {
return null;
}
} else {
return null;
}
}
private Response execute(final String url,
final String body,
final String method,
final Map additionalHeaders,
final Class clazz) {
HttpURLConnection httpUrlConnection = null;
try {
httpUrlConnection = createHttpUrlConnection(url);
try (final InputStream inputStream = getInputStream(httpUrlConnection, body, method, additionalHeaders)) {
final String responseBody = StringUtils.streamToString(inputStream, Charset.forName(encoding));
return new Response<>(httpUrlConnection.getResponseCode(),
convertResponseFromString(responseBody, clazz),
responseBody,
httpUrlConnection.getHeaderFields());
}
} catch (final Exception e) {
String msg = "An unknown error occurred when performing the operation";
int statusCode = -1;
String errorResponse = null;
if (nonNull(httpUrlConnection)) {
try {
statusCode = httpUrlConnection.getResponseCode();
errorResponse = StringUtils.streamToString(httpUrlConnection.getErrorStream(),
Charset.forName(encoding));
msg = "An error occurred when performing the operation ("
+ httpUrlConnection.getResponseCode() + "): "
+ errorResponse;
} catch (final IOException | NullPointerException ignored) {
}
}
throw new HttpClientException(msg, e, url, statusCode, errorResponse);
} finally {
if (nonNull(httpUrlConnection)) {
httpUrlConnection.disconnect();
}
}
}
/**
* Builder for a {@link DefaultHttpClient}, see docs for that class for example usage.
*/
public static class Builder {
private String encoding = "UTF-8";
private String contentType = "application/json";
private int timeout = 4000;
private int retryCount = 0;
/**
* Default encoding for requests.
*/
public Builder withEncoding(final String encoding) {
this.encoding = encoding;
return this;
}
/**
* Default contentType header for requests.
*/
public Builder withContentType(final String contentType) {
this.contentType = contentType;
return this;
}
/**
* Default timeout for requests.
*/
public Builder withTimeout(final int timeout) {
this.timeout = timeout;
return this;
}
/**
* Default retry count for GET requests.
*/
public Builder withRetryCount(final int retryCount) {
this.retryCount = retryCount;
return this;
}
/**
* Build the {@link DefaultHttpClient}.
*/
public DefaultHttpClient build() {
return new DefaultHttpClient(this);
}
}
/**
* Override this method if you would like to implement a custom URL connection.
*
* @param url The URL to connect to.
* @return A URL connection.
* @throws IOException An I/O exception occurred.
*/
protected HttpURLConnection createHttpUrlConnection(final String url)
throws IOException {
return (HttpURLConnection) new URL(url).openConnection();
}
/**
* Rebuild the URL with parameters appended.
*
* @param url The URL to rebuild.
* @param parameters The parameters to append.
* @return The parameter-appended URL.
* @throws UnsupportedEncodingException An encoding exception occurred.
*/
protected String urlWithParameters(final String url,
final List parameters)
throws UnsupportedEncodingException {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(url);
if (nonNull(parameters) && !parameters.isEmpty()) {
boolean first = true;
for (final Parameter parameter : parameters) {
if (!first) {
stringBuilder.append("&");
} else {
stringBuilder.append("?");
first = false;
}
stringBuilder.append(URLEncoder.encode(parameter.getName(), encoding));
stringBuilder.append("=");
stringBuilder.append(URLEncoder.encode(parameter.getValue(), encoding));
}
}
return stringBuilder.toString();
}
/**
* Initialize the connection with the given parameters, then return the provisioned input stream.
*
* @param httpUrlConnection The connection to initialize.
* @param body The request body for the stream.
* @param method The HTTP method.
* @param additionalHeaders Additional headers for the request.
* @return The provisioned input stream.
* @throws IOException An I/O exception occurred.
*/
protected InputStream getInputStream(final HttpURLConnection httpUrlConnection,
final String body,
final String method,
final Map additionalHeaders)
throws IOException {
httpUrlConnection.setRequestMethod(method);
httpUrlConnection.setConnectTimeout(timeout);
httpUrlConnection.setReadTimeout(timeout);
appendDefaultsToConnection(httpUrlConnection, additionalHeaders);
modifyConnection(httpUrlConnection);
if (isNotBlank(body)) {
httpUrlConnection.setDoOutput(true);
httpUrlConnection.connect();
final OutputStream outputStream = httpUrlConnection.getOutputStream();
outputStream.write(body.getBytes(Charset.forName(encoding)));
outputStream.close();
} else {
httpUrlConnection.setRequestProperty("Content-Length", "0");
httpUrlConnection.connect();
}
return httpUrlConnection.getInputStream();
}
}