/* * Copyright 2015 LINE Corporation * * LINE Corporation licenses this file to you 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 com.linecorp.armeria.client; import static java.util.Objects.requireNonNull; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.net.URI; import java.util.Optional; import java.util.function.Function; import com.linecorp.armeria.common.http.HttpHeaders; import com.linecorp.armeria.common.util.SafeCloseable; import io.netty.util.AsciiString; /** * Creates a new client that connects to a specified {@link URI}. */ public final class Clients { /** * Creates a new client that connects to the specified {@code uri} using the default * {@link ClientFactory}. * * @param uri the URI of the server endpoint * @param clientType the type of the new client * @param options the {@link ClientOptionValue}s * * @throws IllegalArgumentException if the scheme of the specified {@code uri} or * the specified {@code clientType} is unsupported for the scheme */ public static <T> T newClient(String uri, Class<T> clientType, ClientOptionValue<?>... options) { return newClient(ClientFactory.DEFAULT, uri, clientType, options); } /** * Creates a new client that connects to the specified {@code uri} using the default * {@link ClientFactory}. * * @param uri the URI of the server endpoint * @param clientType the type of the new client * @param options the {@link ClientOptions} * * @throws IllegalArgumentException if the scheme of the specified {@code uri} or * the specified {@code clientType} is unsupported for the scheme */ public static <T> T newClient(String uri, Class<T> clientType, ClientOptions options) { return newClient(ClientFactory.DEFAULT, uri, clientType, options); } /** * Creates a new client that connects to the specified {@code uri} using an alternative * {@link ClientFactory}. * * @param factory an alternative {@link ClientFactory} * @param uri the URI of the server endpoint * @param clientType the type of the new client * @param options the {@link ClientOptionValue}s * * @throws IllegalArgumentException if the scheme of the specified {@code uri} or * the specified {@code clientType} is unsupported for the scheme */ public static <T> T newClient(ClientFactory factory, String uri, Class<T> clientType, ClientOptionValue<?>... options) { return new ClientBuilder(uri).factory(factory).options(options).build(clientType); } /** * Creates a new client that connects to the specified {@code uri} using an alternative * {@link ClientFactory}. * * @param factory an alternative {@link ClientFactory} * @param uri the URI of the server endpoint * @param clientType the type of the new client * @param options the {@link ClientOptions} * * @throws IllegalArgumentException if the scheme of the specified {@code uri} or * the specified {@code clientType} is unsupported for the scheme */ public static <T> T newClient(ClientFactory factory, String uri, Class<T> clientType, ClientOptions options) { return new ClientBuilder(uri).factory(factory).options(options).build(clientType); } /** * Creates a new client that connects to the specified {@link URI} using the default * {@link ClientFactory}. * * @param uri the URI of the server endpoint * @param clientType the type of the new client * @param options the {@link ClientOptionValue}s * * @throws IllegalArgumentException if the scheme of the specified {@code uri} or * the specified {@code clientType} is unsupported for the scheme */ public static <T> T newClient(URI uri, Class<T> clientType, ClientOptionValue<?>... options) { return newClient(ClientFactory.DEFAULT, uri, clientType, options); } /** * Creates a new client that connects to the specified {@link URI} using the default * {@link ClientFactory}. * * @param uri the URI of the server endpoint * @param clientType the type of the new client * @param options the {@link ClientOptions} * * @throws IllegalArgumentException if the scheme of the specified {@code uri} or * the specified {@code clientType} is unsupported for the scheme */ public static <T> T newClient(URI uri, Class<T> clientType, ClientOptions options) { return newClient(ClientFactory.DEFAULT, uri, clientType, options); } /** * Creates a new client that connects to the specified {@link URI} using an alternative * {@link ClientFactory}. * * @param factory an alternative {@link ClientFactory} * @param uri the URI of the server endpoint * @param clientType the type of the new client * @param options the {@link ClientOptionValue}s * * @throws IllegalArgumentException if the scheme of the specified {@code uri} or * the specified {@code clientType} is unsupported for the scheme */ public static <T> T newClient(ClientFactory factory, URI uri, Class<T> clientType, ClientOptionValue<?>... options) { return new ClientBuilder(uri).factory(factory).options(options).build(clientType); } /** * Creates a new client that connects to the specified {@link URI} using an alternative * {@link ClientFactory}. * * @param factory an alternative {@link ClientFactory} * @param uri the URI of the server endpoint * @param clientType the type of the new client * @param options the {@link ClientOptions} * * @throws IllegalArgumentException if the scheme of the specified {@code uri} or * the specified {@code clientType} is unsupported for the scheme */ public static <T> T newClient(ClientFactory factory, URI uri, Class<T> clientType, ClientOptions options) { return new ClientBuilder(uri).factory(factory).options(options).build(clientType); } /** * Creates a new derived client that connects to the same {@link URI} with the specified {@code client} * and the specified {@code additionalOptions}. * * @see ClientBuilder ClientBuilder, for more information about how the base options and * additional options are merged when a derived client is created. */ public static <T> T newDerivedClient(T client, ClientOptionValue<?>... additionalOptions) { final ClientBuilderParams params = builderParams(client); final ClientBuilder builder = newDerivedBuilder(params); builder.options(additionalOptions); return newDerivedClient(builder, params.clientType()); } /** * Creates a new derived client that connects to the same {@link URI} with the specified {@code client} * and the specified {@code additionalOptions}. * * @see ClientBuilder ClientBuilder, for more information about how the base options and * additional options are merged when a derived client is created. */ public static <T> T newDerivedClient(T client, Iterable<ClientOptionValue<?>> additionalOptions) { final ClientBuilderParams params = builderParams(client); final ClientBuilder builder = newDerivedBuilder(params); builder.options(additionalOptions); return newDerivedClient(builder, params.clientType()); } /** * Creates a new derived client that connects to the same {@link URI} with the specified {@code client} * but with different {@link ClientOption}s. For example: * * <pre>{@code * HttpClient derivedHttpClient = Clients.newDerivedClient(httpClient, options -> { * ClientOptionsBuilder builder = new ClientOptionsBuilder(options); * builder.decorator(...); // Add a decorator. * builder.httpHeader(...); // Add an HTTP header. * return builder.build(); * }); * }</pre> * * @param configurator a {@link Function} whose input is the original {@link ClientOptions} of the client * being derived from and whose output is the {@link ClientOptions} of the new derived * client * * @see ClientBuilder ClientBuilder, for more information about how the base options and * additional options are merged when a derived client is created. * @see ClientOptionsBuilder */ public static <T> T newDerivedClient( T client, Function<? super ClientOptions, ClientOptions> configurator) { final ClientBuilderParams params = builderParams(client); final ClientBuilder builder = new ClientBuilder(params.uri()); builder.factory(params.factory()); builder.options(configurator.apply(params.options())); return newDerivedClient(builder, params.clientType()); } @SuppressWarnings("unchecked") private static <T> T newDerivedClient(ClientBuilder builder, Class<?> clientType) { return builder.build((Class<T>) clientType); } private static ClientBuilder newDerivedBuilder(ClientBuilderParams params) { final ClientBuilder builder = new ClientBuilder(params.uri()); builder.factory(params.factory()); builder.options(params.options()); return builder; } private static ClientBuilderParams builderParams(Object client) { requireNonNull(client, "client"); if (client instanceof ClientBuilderParams) { return (ClientBuilderParams) client; } if (Proxy.isProxyClass(client.getClass())) { final InvocationHandler handler = Proxy.getInvocationHandler(client); if (handler instanceof ClientBuilderParams) { return (ClientBuilderParams) handler; } } Optional<ClientBuilderParams> params = ClientFactory.DEFAULT.clientBuilderParams(client); if (params.isPresent()) { return params.get(); } throw new IllegalArgumentException("derivation not supported by: " + client.getClass().getName()); } /** * Sets the specified HTTP header in a thread-local variable so that the header is sent by the client call * made from the current thread. Use the `try-resources-finally` block with the returned * {@link SafeCloseable} to unset the thread-local variable automatically: * <pre>{@code * import static com.linecorp.armeria.common.http.HttpHeaderNames.AUTHORIZATION; * * try (SafeCloseable ignored = withHttpHeader(AUTHORIZATION, myCredential)) { * client.executeSomething(..); * } * }</pre> * You can also nest the header manipulation: * <pre>{@code * import static com.linecorp.armeria.common.http.HttpHeaderNames.AUTHORIZATION; * import static com.linecorp.armeria.common.http.HttpHeaderNames.USER_AGENT; * * try (SafeCloseable ignored = withHttpHeader(USER_AGENT, myAgent)) { * for (String secret : secrets) { * try (SafeCloseable ignored2 = withHttpHeader(AUTHORIZATION, secret)) { * // Both USER_AGENT and AUTHORIZATION will be set. * client.executeSomething(..); * } * } * } * }</pre> * * @see #withHttpHeaders(Function) */ public static SafeCloseable withHttpHeader(AsciiString name, String value) { requireNonNull(name, "name"); requireNonNull(value, "value"); return withHttpHeaders(headers -> headers.set(name, value)); } /** * Sets the specified HTTP header manipulating function in a thread-local variable so that the manipulated * headers are sent by the client call made from the current thread. Use the `try-resources-finally` block * with the returned {@link SafeCloseable} to unset the thread-local variable automatically: * <pre>{@code * import static com.linecorp.armeria.common.http.HttpHeaderNames.AUTHORIZATION; * import static com.linecorp.armeria.common.http.HttpHeaderNames.USER_AGENT; * * try (SafeCloseable ignored = withHttpHeaders(headers -> { * headers.set(HttpHeaders.AUTHORIZATION, myCredential) * .set(HttpHeaders.USER_AGENT, myAgent); * }) { * client.executeSomething(..); * } * }</pre> * You can also nest the header manipulation: * <pre>{@code * import static com.linecorp.armeria.common.http.HttpHeaderNames.AUTHORIZATION; * import static com.linecorp.armeria.common.http.HttpHeaderNames.USER_AGENT; * * try (SafeCloseable ignored = withHttpHeaders(h -> h.set(USER_AGENT, myAgent)) { * for (String secret : secrets) { * try (SafeCloseable ignored2 = withHttpHeaders(h -> h.set(AUTHORIZATION, secret)) { * // Both USER_AGENT and AUTHORIZATION will be set. * client.executeSomething(..); * } * } * } * }</pre> * * @see #withHttpHeader(AsciiString, String) */ public static SafeCloseable withHttpHeaders(Function<HttpHeaders, HttpHeaders> headerManipulator) { requireNonNull(headerManipulator, "headerManipulator"); final Function<HttpHeaders, HttpHeaders> oldManipulator = UserClient.THREAD_LOCAL_HEADER_MANIPULATOR.get(); if (oldManipulator != null) { UserClient.THREAD_LOCAL_HEADER_MANIPULATOR.set(oldManipulator.andThen(headerManipulator)); return () -> UserClient.THREAD_LOCAL_HEADER_MANIPULATOR.set(oldManipulator); } else { UserClient.THREAD_LOCAL_HEADER_MANIPULATOR.set(headerManipulator); return UserClient.THREAD_LOCAL_HEADER_MANIPULATOR::remove; } } private Clients() {} }