io.datarouter.httpclient.client.DatarouterHttpCallTool Maven / Gradle / Ivy
The newest version!
/*
* Copyright © 2009 HotPads ([email protected])
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.datarouter.httpclient.client;
import java.io.IOException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.datarouter.httpclient.request.DatarouterHttpRequest;
import io.datarouter.httpclient.response.DatarouterHttpResponse;
import io.datarouter.httpclient.response.exception.DatarouterHttpConnectionAbortedException;
import io.datarouter.httpclient.response.exception.DatarouterHttpException;
import io.datarouter.httpclient.response.exception.DatarouterHttpRequestInterruptedException;
import io.datarouter.httpclient.response.exception.DatarouterHttpResponseException;
import io.datarouter.instrumentation.trace.TraceSpanGroupType;
import io.datarouter.instrumentation.trace.TraceTimeTool;
import io.datarouter.instrumentation.trace.Traceparent;
import io.datarouter.instrumentation.trace.Tracer;
import io.datarouter.instrumentation.trace.TracerThreadLocal;
import io.datarouter.instrumentation.trace.TracerTool;
import io.datarouter.instrumentation.trace.W3TraceContext;
public class DatarouterHttpCallTool{
private static final Logger logger = LoggerFactory.getLogger(DatarouterHttpCallTool.class);
private static final Duration DEFAULT_LOG_SLOW_REQUEST_THRESHOLD = Duration.ofSeconds(1);
public static final String TRACEPARENT = "traceparent";
public static final String TRACESTATE = "tracestate";
public static DatarouterHttpResponse call(
String name,
CloseableHttpClient httpClient,
DatarouterHttpRequest request,
Consumer httpEntityConsumer,
HttpClientContext context,
Supplier traceInQueryString,
Supplier debugLog)
throws DatarouterHttpException{
DatarouterHttpException ex;
TracerTool.startSpan("http call " + request.getPath(), TraceSpanGroupType.HTTP);
Tracer tracer = TracerThreadLocal.get();
W3TraceContext traceContext;
if(tracer != null && tracer.getTraceContext().isPresent()){
traceContext = tracer.getTraceContext().get().copy();
traceContext.updateParentIdAndAddTracestateMember();
}else{
DatarouterHttpClientMetrics.incTraceContextNull(name);
traceContext = new W3TraceContext(TraceTimeTool.epochNano());
}
String traceparent = traceContext.getTraceparent().toString();
if(traceInQueryString.get()){
request.addGetParam(TRACEPARENT, traceparent);
}
HttpRequestBase internalHttpRequest = request.getRequest();
DatarouterHttpClientMetrics.incRequest(name);
logger.debug("traceparent={} passing to request={}", traceparent, request.getPath());
internalHttpRequest.addHeader(TRACEPARENT, traceparent);
internalHttpRequest.addHeader(TRACESTATE, traceContext.getTracestate().toString());
context.setAttribute(TRACEPARENT, traceContext.getTraceparent().toString());
if(debugLog.get()){
logger.warn("sending http request method={} url={}",
internalHttpRequest.getMethod(),
internalHttpRequest.getURI());
}
long requestStartTimeNs = TraceTimeTool.epochNano();
try{
HttpResponse httpResponse = httpClient.execute(internalHttpRequest, context);
String entity = null;
int statusCode = httpResponse.getStatusLine().getStatusCode();
DatarouterHttpClientMetrics.incResponseStatusCode(name, statusCode);
boolean isBadStatusCode = statusCode >= HttpStatus.SC_BAD_REQUEST;
HttpEntity httpEntity = httpResponse.getEntity();
if(httpEntity != null){
// skip the httpEntityConsumer in case of error because we are going to close the input stream
if(httpEntityConsumer != null && !isBadStatusCode){
httpEntityConsumer.accept(httpEntity);
}else{
entity = EntityUtils.toString(httpEntity);
}
}
// include the entity processing. might be inaccurate in case of custom httpEntityConsumer
Duration duration = Duration.ofNanos(TraceTimeTool.epochNano() - requestStartTimeNs);
DatarouterHttpClientMetrics.durationMs(name, duration.toMillis());
Optional remoteTraceparent = Optional.ofNullable(httpResponse.getFirstHeader(TRACEPARENT))
.map(Header::getValue)
.map(Traceparent::parseIfValid)
.filter(Optional::isPresent)
.map(Optional::get);
remoteTraceparent.ifPresent(tp -> TracerTool.appendToSpanInfo("remote parentId", tp.parentId));
if(remoteTraceparent.isPresent() && remoteTraceparent.get().shouldSample()){
// if remote server has forced sample for trace, we also force sample the client's trace
TracerTool.setForceSample();
}
Duration logSlowRequestThreshold = request.findLogSlowRequestThreshold()
.orElse(DEFAULT_LOG_SLOW_REQUEST_THRESHOLD);
if(duration.compareTo(logSlowRequestThreshold) > 0){
DatarouterHttpClientMetrics.incSlowRequest(name);
logger.warn("Slow request target={} durationMs={} remoteTraceparent={}", request.getPath(),
duration.toMillis(), remoteTraceparent.orElse(null));
}
if(debugLog.get()){
logger.warn("request url={} response cookies={}",
internalHttpRequest.getURI(),
Arrays.toString(httpResponse.getHeaders("Set-Cookie")));
}
DatarouterHttpResponse response = new DatarouterHttpResponse(httpResponse, context, statusCode, entity);
if(isBadStatusCode){
TracerTool.appendToSpanInfo("bad status code", statusCode);
ex = new DatarouterHttpResponseException(response, duration, traceparent, request.getPath());
// no need to abort the connection, we received a response line, the connection is probably still good
response.tryClose();
throw ex;
}
return response;
}catch(IOException e){
DatarouterHttpClientMetrics.incIoException(name);
TracerTool.appendToSpanInfo("exception", e.getMessage());
ex = new DatarouterHttpConnectionAbortedException(e, TimeUnit.NANOSECONDS.toMillis(requestStartTimeNs),
traceparent, request.getPath());
}catch(CancellationException e){
DatarouterHttpClientMetrics.incCancellationException(name);
TracerTool.appendToSpanInfo("exception", e.getMessage());
ex = new DatarouterHttpRequestInterruptedException(e, TimeUnit.NANOSECONDS.toMillis(requestStartTimeNs),
traceparent, request.getPath());
}finally{
TracerTool.finishSpan();
}
// connection might have gone bad, destroying it
if(internalHttpRequest != null){
forceAbortRequestUnchecked(internalHttpRequest);
}
throw ex;
}
private static void forceAbortRequestUnchecked(HttpRequestBase internalHttpRequest){
try{
internalHttpRequest.abort();
}catch(Exception e){
logger.error("aborting internal http request failed", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy