All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micrometer.java11.instrument.binder.jdk.MicrometerHttpClient Maven / Gradle / Ivy

There is a newer version: 1.14.1
Show newest version
/*
 * Copyright 2024 VMware, Inc.
 *
 * 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
 *
 * https://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.micrometer.java11.instrument.binder.jdk;

import io.micrometer.common.lang.Nullable;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.observation.ObservationOrTimerCompatibleInstrumentation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import java.io.IOException;
import java.net.Authenticator;
import java.net.CookieHandler;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.Function;

/**
 * Delegates to an {@link HttpClient} while instrumenting with Micrometer any HTTP calls
 * made. Example setup: 
{@code
 * HttpClient observedClient = MicrometerHttpClient.instrumentationBuilder(HttpClient.newHttpClient(), meterRegistry).build();
 * }
* * Inspired by interceptable-http-client. * * @author Marcin Grzejszczak * @since 1.13.0 */ public class MicrometerHttpClient extends HttpClient { /** * Header name for URI pattern. */ public static final String URI_PATTERN_HEADER = "URI_PATTERN"; private final MeterRegistry meterRegistry; private final HttpClient client; @Nullable private final ObservationRegistry observationRegistry; @Nullable private final HttpClientObservationConvention customObservationConvention; private final Function uriMapper; private MicrometerHttpClient(MeterRegistry meterRegistry, HttpClient client, @Nullable ObservationRegistry observationRegistry, @Nullable HttpClientObservationConvention customObservationConvention, Function uriMapper) { this.meterRegistry = meterRegistry; this.client = client; this.observationRegistry = observationRegistry; this.customObservationConvention = customObservationConvention; this.uriMapper = uriMapper; } /** * Builder for instrumentation of {@link HttpClient}. * @param httpClient HttpClient to wrap * @param meterRegistry meter registry * @return builder */ public static InstrumentationBuilder instrumentationBuilder(HttpClient httpClient, MeterRegistry meterRegistry) { return new InstrumentationBuilder(httpClient, meterRegistry); } /** * Builder for {@link MicrometerHttpClient}. */ public static class InstrumentationBuilder { private final HttpClient client; private final MeterRegistry meterRegistry; @Nullable private ObservationRegistry observationRegistry; @Nullable private HttpClientObservationConvention customObservationConvention; private Function uriMapper = request -> request.headers() .firstValue(URI_PATTERN_HEADER) .orElse("UNKNOWN"); /** * Creates a new instance of {@link InstrumentationBuilder}. * @param client client to wrap * @param meterRegistry a {@link MeterRegistry} */ public InstrumentationBuilder(HttpClient client, MeterRegistry meterRegistry) { this.client = client; this.meterRegistry = meterRegistry; } /** * Set {@link ObservationRegistry} if you want to use {@link Observation}. * @param observationRegistry observation registry * @return this */ public InstrumentationBuilder observationRegistry(ObservationRegistry observationRegistry) { this.observationRegistry = observationRegistry; return this; } /** * When used with {@link ObservationRegistry}, it will override the default * {@link HttpClientObservationConvention}. * @param customObservationConvention custom observation convention * @return this */ public InstrumentationBuilder customObservationConvention( HttpClientObservationConvention customObservationConvention) { this.customObservationConvention = customObservationConvention; return this; } /** * Provides custom URI mapper mechanism. * @param uriMapper URI mapper * @return this */ public InstrumentationBuilder uriMapper(Function uriMapper) { this.uriMapper = uriMapper; return this; } /** * Builds a wrapped {@link HttpClient}. * @return a wrapped {@link HttpClient} */ public HttpClient build() { return new MicrometerHttpClient(this.meterRegistry, this.client, this.observationRegistry, this.customObservationConvention, this.uriMapper); } } @Override public Optional cookieHandler() { return client.cookieHandler(); } @Override public Optional connectTimeout() { return client.connectTimeout(); } @Override public Redirect followRedirects() { return client.followRedirects(); } @Override public Optional proxy() { return client.proxy(); } @Override public SSLContext sslContext() { return client.sslContext(); } @Override public SSLParameters sslParameters() { return client.sslParameters(); } @Override public Optional authenticator() { return client.authenticator(); } @Override public Version version() { return client.version(); } @Override public Optional executor() { return client.executor(); } @Override public HttpResponse send(HttpRequest httpRequest, HttpResponse.BodyHandler bodyHandler) throws IOException, InterruptedException { HttpRequest.Builder httpRequestBuilder = decorate(httpRequest); ObservationOrTimerCompatibleInstrumentation instrumentation = observationOrTimer( httpRequestBuilder); HttpRequest request = httpRequestBuilder.build(); HttpResponse response = null; try { response = client.send(request, bodyHandler); instrumentation.setResponse(response); return response; } catch (IOException ex) { instrumentation.setThrowable(ex); throw ex; } finally { stopObservationOrTimer(instrumentation, request, response); } } private void stopObservationOrTimer( ObservationOrTimerCompatibleInstrumentation instrumentation, HttpRequest request, @Nullable HttpResponse res) { instrumentation.stop(DefaultHttpClientObservationConvention.INSTANCE.getName(), "Timer for JDK's HttpClient", () -> Tags.of(HttpClientObservationDocumentation.LowCardinalityKeys.METHOD.asString(), request.method(), HttpClientObservationDocumentation.LowCardinalityKeys.URI.asString(), DefaultHttpClientObservationConvention.INSTANCE.getUri(request, res, uriMapper), HttpClientObservationDocumentation.LowCardinalityKeys.STATUS.asString(), DefaultHttpClientObservationConvention.INSTANCE.getStatus(res), HttpClientObservationDocumentation.LowCardinalityKeys.OUTCOME.asString(), DefaultHttpClientObservationConvention.INSTANCE.getOutcome(res))); } private ObservationOrTimerCompatibleInstrumentation observationOrTimer( HttpRequest.Builder httpRequestBuilder) { return ObservationOrTimerCompatibleInstrumentation.start(this.meterRegistry, this.observationRegistry, () -> { HttpClientContext context = new HttpClientContext(this.uriMapper); context.setCarrier(httpRequestBuilder); return context; }, this.customObservationConvention, DefaultHttpClientObservationConvention.INSTANCE); } @Override public CompletableFuture> sendAsync(HttpRequest httpRequest, HttpResponse.BodyHandler bodyHandler) { return sendAsync(httpRequest, bodyHandler, null); } @Override public CompletableFuture> sendAsync(HttpRequest httpRequest, HttpResponse.BodyHandler bodyHandler, @Nullable HttpResponse.PushPromiseHandler pushPromiseHandler) { HttpRequest.Builder httpRequestBuilder = decorate(httpRequest); ObservationOrTimerCompatibleInstrumentation instrumentation = observationOrTimer( httpRequestBuilder); HttpRequest request = httpRequestBuilder.build(); return client.sendAsync(request, bodyHandler, pushPromiseHandler).handle((response, throwable) -> { instrumentation.setResponse(response); if (throwable != null) { instrumentation.setThrowable(throwable); stopObservationOrTimer(instrumentation, request, response); throw new CompletionException(throwable); } else { stopObservationOrTimer(instrumentation, request, response); return response; } }); } private HttpRequest.Builder decorate(HttpRequest httpRequest) { HttpRequest.Builder builder = HttpRequest.newBuilder(httpRequest.uri()); builder.expectContinue(httpRequest.expectContinue()); httpRequest.headers().map().forEach((key, values) -> values.forEach(value -> builder.header(key, value))); httpRequest.bodyPublisher() .ifPresentOrElse(publisher -> builder.method(httpRequest.method(), publisher), () -> { switch (httpRequest.method()) { case "GET": builder.GET(); break; case "DELETE": builder.DELETE(); break; default: throw new IllegalStateException(httpRequest.method()); } }); httpRequest.timeout().ifPresent(builder::timeout); httpRequest.version().ifPresent(builder::version); return builder; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy