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.
package se.fortnox.reactivewizard.client;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.collect.Sets;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.timeout.ReadTimeoutException;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClientResponse;
import reactor.util.retry.Retry;
import rx.Observable;
import rx.RxReactiveStreams;
import rx.Single;
import se.fortnox.reactivewizard.jaxrs.ByteBufCollector;
import se.fortnox.reactivewizard.jaxrs.FieldError;
import se.fortnox.reactivewizard.jaxrs.JaxRsMeta;
import se.fortnox.reactivewizard.jaxrs.WebException;
import se.fortnox.reactivewizard.metrics.HealthRecorder;
import se.fortnox.reactivewizard.metrics.PublisherMetrics;
import se.fortnox.reactivewizard.util.JustMessageException;
import se.fortnox.reactivewizard.util.ReflectionUtil;
import javax.inject.Inject;
import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import static io.netty.handler.codec.http.HttpMethod.POST;
import static io.netty.handler.codec.http.HttpResponseStatus.GATEWAY_TIMEOUT;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
import static reactor.core.Exceptions.isRetryExhausted;
import static rx.Observable.fromCallable;
import static se.fortnox.reactivewizard.jaxrs.RequestLogger.getHeaderValuesOrRedact;
public class HttpClient implements InvocationHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);
private static final Class BYTEARRAY_TYPE = (new byte[0]).getClass();
public static final String COOKIE = "Cookie";
protected final InetSocketAddress serverInfo;
protected final HttpClientConfig config;
private final ByteBufCollector collector;
private final RequestParameterSerializers requestParameterSerializers;
private final Set preRequestHooks;
private final ReactorRxClientProvider clientProvider;
private final ObjectMapper objectMapper;
private final Map, List> beanParamCache = new HashMap<>();
private final Map jaxRsMetaMap = new ConcurrentHashMap<>();
private int timeout = 10;
private TemporalUnit timeoutUnit = ChronoUnit.SECONDS;
private final Set sensitiveHeaders = new TreeSet<>(Comparator.comparing(String::toLowerCase));
private final Duration retryDuration;
@Inject
public HttpClient(HttpClientConfig config,
ReactorRxClientProvider clientProvider,
ObjectMapper objectMapper,
RequestParameterSerializers requestParameterSerializers,
Set preRequestHooks
) {
this.config = config;
this.clientProvider = clientProvider;
this.objectMapper = objectMapper;
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.requestParameterSerializers = requestParameterSerializers;
serverInfo = new InetSocketAddress(config.getHost(), config.getPort());
collector = new ByteBufCollector(config.getMaxResponseSize());
this.preRequestHooks = preRequestHooks;
this.retryDuration = Duration.ofMillis(config.getRetryDelayMs());
}
public HttpClient(HttpClientConfig config) {
this(config, new ReactorRxClientProvider(config, new HealthRecorder()), new ObjectMapper(), new RequestParameterSerializers(), emptySet());
}
public static void setTimeout(Object proxy, int timeout, ChronoUnit timeoutUnit) {
ifHttpClientDo(proxy, httpClient -> httpClient.setTimeout(timeout, timeoutUnit));
}
public void setTimeout(int timeout, ChronoUnit timeoutUnit) {
this.timeout = timeout;
this.timeoutUnit = timeoutUnit;
}
public static void markHeaderAsSensitive(Object proxy, String header) {
markHeadersAsSensitive(proxy, singleton(header));
}
public static void markHeadersAsSensitive(Object proxy, Set headers) {
ifHttpClientDo(proxy, httpClient -> httpClient.addSensitiveHeaders(headers));
}
private static void ifHttpClientDo(Object proxy, Consumer consumer) {
if (Proxy.isProxyClass(proxy.getClass())) {
Object handler = Proxy.getInvocationHandler(proxy);
if (handler instanceof HttpClient) {
consumer.accept((HttpClient) handler);
}
}
}
public void addSensitiveHeaders(Set headers) {
this.sensitiveHeaders.addAll(headers);
}
@SuppressWarnings("unchecked")
public T create(Class jaxRsInterface) {
return (T)Proxy.newProxyInstance(jaxRsInterface.getClassLoader(), new Class[]{jaxRsInterface}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) {
if (arguments == null) {
arguments = new Object[0];
}
RequestBuilder request = createRequest(method, arguments);
addDevOverrides(request);
addAuthenticationHeaders(request);
reactor.netty.http.client.HttpClient rxClient = clientProvider.clientFor(request.getServerInfo());
Mono response = request.submit(rxClient, request);
Publisher> publisher = null;
AtomicReference rawResponse = new AtomicReference<>();
if (expectsByteArrayResponse(method)) {
publisher = response.flatMap(rwHttpClientResponse -> {
rawResponse.set(rwHttpClientResponse.getHttpClientResponse());
if (rwHttpClientResponse.getHttpClientResponse().status().code() >= 400) {
return Mono.from(collector.collectString(rwHttpClientResponse.getContent()))
.map(data -> handleError(request, rwHttpClientResponse.getHttpClientResponse(), data).getBytes());
}
return Mono.from(collector.collectBytes(rwHttpClientResponse.getContent()));
});
} else {
publisher = response.flatMap(rwHttpClientResponse -> {
rawResponse.set(rwHttpClientResponse.getHttpClientResponse());
return parseResponse(method, request, rwHttpClientResponse);
});
}
publisher = measure(request, publisher);
//End of publisher
Flux> flux = Flux.from(publisher);
flux = flux.timeout(Duration.of(timeout, timeoutUnit));
publisher = withRetry(request, flux).onErrorResume(e -> convertError(request, e));
if (Single.class.isAssignableFrom(method.getReturnType())) {
return new SingleWithResponse(RxReactiveStreams.toSingle(publisher), rawResponse);
}
return new ObservableWithResponse(RxReactiveStreams.toObservable(publisher), rawResponse);
}
/**
* Should be used with an observable coming directly from another api-call to get access to meta data, such as status and headers
* from the response.
*
* @param source the source observable, must be observable returned from api call
* @param the type of data that should be returned in the call
* @return an observable that along with the data passes the response meta data
*/
public static Observable> getFullResponse(Observable source) {
if (!(source instanceof ObservableWithResponse)) {
throw new IllegalArgumentException("Must be used with observable returned from api call");
}
return source.map(data -> new Response<>(((ObservableWithResponse) source).getResponse(), data))
.switchIfEmpty(fromCallable(() -> new Response<>(((ObservableWithResponse)source).getResponse(), null)));
}
/**
* Should be used with a Single coming directly from another api-call to get access to meta data, such as status and header
*
* @param source the source observable, must be observable returned from api call
* @param the type of data that should be returned in the call
* @return an observable that along with the data passes the response object from netty
*/
public static Single> getFullResponse(Single source) {
if (!(source instanceof SingleWithResponse)) {
throw new IllegalArgumentException("Must be used with single returned from api call");
}
return source
.map(data -> new Response<>(((SingleWithResponse)source).getResponse(), data));
}
private Flux convertError(RequestBuilder fullReq, Throwable throwable) {
String request = format("%s, headers: %s", fullReq.getFullUrl(), getHeaderValuesOrRedact(fullReq.getHeaders(), sensitiveHeaders));
LOG.warn("Failed request. Url: {}", request, throwable);
if (isRetryExhausted(throwable)) {
throwable = throwable.getCause();
}
if (throwable instanceof TimeoutException || throwable instanceof ReadTimeoutException) {
String message = format("Timeout after %d ms calling %s", Duration.of(timeout, timeoutUnit).toMillis(), request);
return Flux.error(new WebException(GATEWAY_TIMEOUT, new JustMessageException(message), false));
} else if (!(throwable instanceof WebException)) {
String message = format("Error calling %s", request);
return Flux.error(new WebException(INTERNAL_SERVER_ERROR, new JustMessageException(message, throwable), false));
}
return Flux.error(throwable);
}
protected Mono