ApacheMoexHttpTransport.java
package org.vlaskin.moexiss.http;
import org.apache.hc.client5.http.HttpResponseException;
import org.apache.hc.client5.http.fluent.Request;
import org.apache.hc.core5.util.Timeout;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Objects;
/** Реализация транспорта на базе Apache HttpComponents с настраиваемыми тайм-аутами. */
public final class ApacheMoexHttpTransport implements MoexHttpTransport
{
/** Тайм-аут установления соединения по умолчанию. */
public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(10);
/** Тайм-аут ожидания ответа по умолчанию. */
public static final Duration DEFAULT_RESPONSE_TIMEOUT = Duration.ofSeconds(30);
private final Timeout connectTimeout;
private final Timeout responseTimeout;
/** Создаёт транспорт со стандартными тайм-аутами. */
public ApacheMoexHttpTransport()
{
this(DEFAULT_CONNECT_TIMEOUT, DEFAULT_RESPONSE_TIMEOUT);
}
/**
* Создаёт транспорт с заданными тайм-аутами.
*
* @param connectTimeout тайм-аут установления соединения, не меньше 1 мс
* @param responseTimeout тайм-аут ожидания ответа, не меньше 1 мс
* @throws NullPointerException если один из тайм-аутов равен {@code null}
* @throws IllegalArgumentException если тайм-аут неположительный или слишком велик
*/
public ApacheMoexHttpTransport(Duration connectTimeout, Duration responseTimeout)
{
this.connectTimeout = toTimeout(connectTimeout, "Connect timeout");
this.responseTimeout = toTimeout(responseTimeout, "Response timeout");
}
@Override
public String get(String url) throws IOException
{
URI requestUri = parseRequestUri(url);
String requestTarget = toSafeRequestTarget(requestUri);
try
{
return Request.get(requestUri)
.connectTimeout(connectTimeout)
.responseTimeout(responseTimeout)
.execute()
.returnContent()
.asString(StandardCharsets.UTF_8);
}
catch (HttpResponseException exception)
{
throw new MoexHttpException(exception.getStatusCode(), requestTarget, exception);
}
catch (IOException exception)
{
throw new IOException("MOEX ISS request failed for " + requestTarget + ": "
+ exception.getMessage(), exception);
}
}
private static Timeout toTimeout(Duration duration, String name)
{
Objects.requireNonNull(duration, name + " must not be null");
long milliseconds;
try
{
milliseconds = duration.toMillis();
}
catch (ArithmeticException exception)
{
throw new IllegalArgumentException(name + " is too large", exception);
}
if (duration.isNegative() || duration.isZero() || milliseconds == 0)
throw new IllegalArgumentException(name + " must be at least 1 ms");
return Timeout.ofMilliseconds(milliseconds);
}
private static URI parseRequestUri(String url) throws IOException
{
if (url == null)
throw new IOException("Invalid request URL");
try
{
URI uri = URI.create(url);
if (!("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme()))
|| uri.getHost() == null)
throw new IllegalArgumentException();
return uri;
}
catch (IllegalArgumentException exception)
{
throw new IOException("Invalid request URL", exception);
}
}
private static String toSafeRequestTarget(URI uri)
{
StringBuilder target = new StringBuilder(uri.getScheme()).append("://").append(uri.getHost());
if (uri.getPort() >= 0)
target.append(':').append(uri.getPort());
if (uri.getRawPath() == null || uri.getRawPath().isEmpty())
return target.append('/').toString();
return target.append(uri.getRawPath()).toString();
}
}