Skip to content

Commit

Permalink
Introduce Preprocessor (#6057)
Browse files Browse the repository at this point in the history
Motivation:

This PR introduces the notion of `Preprocessor`s and allows users to
configure these to clients as options.
The second part of this PR will introduce a way for users to solely
create a client based on `Preprocessor`s. The eventual POC can be found
here: https://github.com/jrhee17/armeria/pull/36/files

Eventually this extension point will also make it easier/clearer for
users to use xDS with existing Armeria APIs.

The full capability/limitations/design of `Preprocessors` are better
described in the following PR: #6051

Modifications:

- Introduced `Preprocessor` and `PreClient` APIs
- Added `ClientPreprocessors` and `ClientPreprocessorsBuilder` to allow
users to easily add `Preprocessor`s to clients as options
- Modified `DefaultWebClient`, `DefaultTHttpClient`, and
`ArmeriaClientCall` to use `Preprocessor`s
- In order to allow users a way to overwrite the chosen `EndpointGroup`,
the `EndpointGroup` is now specified when creating a
`ClientRequestContext` instead of at initialization time.
- Modified `ClientUtil` methods to pass an additional `req` field which
signifies the original request for type-safety.

Result:

- Users can specify `Preprocessor`s when creating a client.

<!--
Visit this URL to learn more about how to write a pull request
description:

https://armeria.dev/community/developer-guide#how-to-write-pull-request-description
-->
  • Loading branch information
jrhee17 authored Jan 15, 2025
1 parent 6562927 commit d72c4ce
Show file tree
Hide file tree
Showing 51 changed files with 1,517 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class AbstractClientOptionsBuilder {

private final Map<ClientOption<?>, ClientOptionValue<?>> options = new LinkedHashMap<>();
private final ClientDecorationBuilder decoration = ClientDecoration.builder();
private final ClientPreprocessorsBuilder clientPreprocessorsBuilder = new ClientPreprocessorsBuilder();
private final HttpHeadersBuilder headers = HttpHeaders.builder();

@Nullable
Expand Down Expand Up @@ -127,6 +128,8 @@ public <T> AbstractClientOptionsBuilder option(ClientOptionValue<T> optionValue)
} else if (opt == ClientOptions.HEADERS) {
final HttpHeaders h = (HttpHeaders) optionValue.value();
setHeaders(h);
} else if (opt == ClientOptions.PREPROCESSORS) {
clientPreprocessorsBuilder.add((ClientPreprocessors) optionValue.value());
} else {
options.put(opt, optionValue);
}
Expand Down Expand Up @@ -520,6 +523,28 @@ public AbstractClientOptionsBuilder responseTimeoutMode(ResponseTimeoutMode resp
requireNonNull(responseTimeoutMode, "responseTimeoutMode"));
}

/**
* Adds the specified HTTP-level {@code preprocessor}.
*
* @param preprocessor the {@link HttpPreprocessor} that preprocesses an invocation
*/
@UnstableApi
public AbstractClientOptionsBuilder preprocessor(HttpPreprocessor preprocessor) {
clientPreprocessorsBuilder.add(preprocessor);
return this;
}

/**
* Adds the specified RPC-level {@code rpcPreprocessor}.
*
* @param rpcPreprocessor the {@link RpcPreprocessor} that preprocesses an invocation
*/
@UnstableApi
public AbstractClientOptionsBuilder rpcPreprocessor(RpcPreprocessor rpcPreprocessor) {
clientPreprocessorsBuilder.addRpc(rpcPreprocessor);
return this;
}

/**
* Builds {@link ClientOptions} with the given options and the
* {@linkplain ClientOptions#of() default options}.
Expand All @@ -538,6 +563,7 @@ protected final ClientOptions buildOptions(@Nullable ClientOptions baseOptions)
ImmutableList.builder();
additionalValues.addAll(optVals);
additionalValues.add(ClientOptions.DECORATION.newValue(decoration.build()));
additionalValues.add(ClientOptions.PREPROCESSORS.newValue(clientPreprocessorsBuilder.build()));
additionalValues.add(ClientOptions.HEADERS.newValue(headers.build()));
additionalValues.add(ClientOptions.CONTEXT_HOOK.newValue(contextHook));
if (contextCustomizer != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,16 @@ public AbstractWebClientBuilder rpcDecorator(Function<? super RpcClient, ? exten
public AbstractWebClientBuilder rpcDecorator(DecoratingRpcClientFunction decorator) {
throw new UnsupportedOperationException("RPC decorator cannot be added to the web client builder.");
}

/**
* Raises an {@link UnsupportedOperationException} because this builder doesn't support RPC-level but only
* HTTP-level preprocessors.
*
* @deprecated RPC preprocessor cannot be added to the web client builder.
*/
@Deprecated
@Override
public AbstractClientOptionsBuilder rpcPreprocessor(RpcPreprocessor rpcPreprocessor) {
throw new UnsupportedOperationException("RPC preprocessor cannot be added to the web client builder.");
}
}
10 changes: 10 additions & 0 deletions core/src/main/java/com/linecorp/armeria/client/ClientBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,14 @@ public ClientBuilder contextHook(Supplier<? extends AutoCloseable> contextHook)
public ClientBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) {
return (ClientBuilder) super.responseTimeoutMode(responseTimeoutMode);
}

@Override
public ClientBuilder preprocessor(HttpPreprocessor decorator) {
return (ClientBuilder) super.preprocessor(decorator);
}

@Override
public ClientBuilder rpcPreprocessor(RpcPreprocessor decorator) {
return (ClientBuilder) super.rpcPreprocessor(decorator);
}
}
21 changes: 21 additions & 0 deletions core/src/main/java/com/linecorp/armeria/client/ClientOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ public final class ClientOptions
public static final ClientOption<ResponseTimeoutMode> RESPONSE_TIMEOUT_MODE =
ClientOption.define("RESPONSE_TIMEOUT_MODE", Flags.responseTimeoutMode());

@UnstableApi
public static final ClientOption<ClientPreprocessors> PREPROCESSORS =
ClientOption.define("PREPROCESSORS", ClientPreprocessors.of(), Function.identity(),
(oldValue, newValue) -> {
final ClientPreprocessors newPreprocessors = newValue.value();
final ClientPreprocessors oldPreprocessors = oldValue.value();
return newValue.option().newValue(
ClientPreprocessors.builder()
.add(oldPreprocessors)
.add(newPreprocessors)
.build());
});

private static final List<AsciiString> PROHIBITED_HEADER_NAMES = ImmutableList.of(
HttpHeaderNames.HTTP2_SETTINGS,
HttpHeaderNames.METHOD,
Expand Down Expand Up @@ -410,6 +423,14 @@ public ResponseTimeoutMode responseTimeoutMode() {
return get(RESPONSE_TIMEOUT_MODE);
}

/**
* Returns the {@link Preprocessor}s that preprocesses the components of a client.
*/
@UnstableApi
public ClientPreprocessors clientPreprocessors() {
return get(PREPROCESSORS);
}

/**
* Returns a new {@link ClientOptionsBuilder} created from this {@link ClientOptions}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,14 @@ public ClientOptionsBuilder contextHook(Supplier<? extends AutoCloseable> contex
public ClientOptionsBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) {
return (ClientOptionsBuilder) super.responseTimeoutMode(responseTimeoutMode);
}

@Override
public ClientOptionsBuilder preprocessor(HttpPreprocessor decorator) {
return (ClientOptionsBuilder) super.preprocessor(decorator);
}

@Override
public ClientOptionsBuilder rpcPreprocessor(RpcPreprocessor decorator) {
return (ClientOptionsBuilder) super.rpcPreprocessor(decorator);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2024 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:
*
* 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 com.linecorp.armeria.client;

import java.util.List;
import java.util.Objects;
import java.util.function.Function;

import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;

import com.linecorp.armeria.common.annotation.UnstableApi;

/**
* A set of {@link Function}s that transforms a {@link HttpPreprocessor} or
* {@link RpcPreprocessor} into another.
*/
@UnstableApi
public final class ClientPreprocessors {

private static final ClientPreprocessors NONE =
new ClientPreprocessors(ImmutableList.of(), ImmutableList.of());

/**
* Returns an empty {@link ClientDecoration} which does not decorate a {@link Client}.
*/
public static ClientPreprocessors of() {
return NONE;
}

/**
* Creates a new instance from a single {@link HttpPreprocessor}.
*
* @param preprocessor the {@link HttpPreprocessor} that transforms an
* {@link HttpPreClient} to another
*/
public static ClientPreprocessors of(HttpPreprocessor preprocessor) {
return builder().add(preprocessor).build();
}

/**
* Creates a new instance from a single {@link RpcPreprocessor}.
*
* @param preprocessor the {@link RpcPreprocessor} that transforms an {@link RpcPreClient}
* to another
*/
public static ClientPreprocessors ofRpc(RpcPreprocessor preprocessor) {
return builder().addRpc(preprocessor).build();
}

/**
* Returns a newly created {@link ClientPreprocessorsBuilder}.
*/
public static ClientPreprocessorsBuilder builder() {
return new ClientPreprocessorsBuilder();
}

private final List<HttpPreprocessor> preprocessors;
private final List<RpcPreprocessor> rpcPreprocessors;

ClientPreprocessors(List<HttpPreprocessor> preprocessors, List<RpcPreprocessor> rpcPreprocessors) {
this.preprocessors = ImmutableList.copyOf(preprocessors);
this.rpcPreprocessors = ImmutableList.copyOf(rpcPreprocessors);
}

/**
* Returns the HTTP-level preprocessors.
*/
public List<HttpPreprocessor> preprocessors() {
return preprocessors;
}

/**
* Returns the RPC-level preprocessors.
*/
public List<RpcPreprocessor> rpcPreprocessors() {
return rpcPreprocessors;
}

/**
* Decorates the specified {@link HttpPreClient} using preprocessors.
*
* @param execution the {@link HttpPreClient} being decorated
*/
public HttpPreClient decorate(HttpPreClient execution) {
for (HttpPreprocessor preprocessor : preprocessors) {
final HttpPreClient execution0 = execution;
execution = (ctx, req) -> preprocessor.execute(execution0, ctx, req);
}
return execution;
}

/**
* Decorates the specified {@link RpcPreClient} using preprocessors.
*
* @param execution the {@link RpcPreClient} being decorated
*/
public RpcPreClient rpcDecorate(RpcPreClient execution) {
for (RpcPreprocessor rpcPreprocessor : rpcPreprocessors) {
final RpcPreClient execution0 = execution;
execution = (ctx, req) -> rpcPreprocessor.execute(execution0, ctx, req);
}
return execution;
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
final ClientPreprocessors that = (ClientPreprocessors) object;
return Objects.equals(preprocessors, that.preprocessors) &&
Objects.equals(rpcPreprocessors, that.rpcPreprocessors);
}

@Override
public int hashCode() {
return Objects.hash(preprocessors, rpcPreprocessors);
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("preprocessors", preprocessors)
.add("rpcPreprocessors", rpcPreprocessors)
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2024 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:
*
* 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 com.linecorp.armeria.client;

import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.List;

import com.linecorp.armeria.common.annotation.UnstableApi;

/**
* Creates a new {@link ClientPreprocessors} using the builder pattern.
*/
@UnstableApi
public final class ClientPreprocessorsBuilder {

private final List<HttpPreprocessor> preprocessors = new ArrayList<>();
private final List<RpcPreprocessor> rpcPreprocessors = new ArrayList<>();

ClientPreprocessorsBuilder() {}

/**
* Adds the specified {@link ClientPreprocessors}.
*/
public ClientPreprocessorsBuilder add(ClientPreprocessors preprocessors) {
requireNonNull(preprocessors, "preprocessors");
preprocessors.preprocessors().forEach(this::add);
preprocessors.rpcPreprocessors().forEach(this::addRpc);
return this;
}

/**
* Adds the specified HTTP-level {@code preprocessor}.
*
* @param preprocessor the {@link HttpPreprocessor} that preprocesses an invocation
*/
public ClientPreprocessorsBuilder add(HttpPreprocessor preprocessor) {
preprocessors.add(requireNonNull(preprocessor, "preprocessor"));
return this;
}

/**
* Adds the specified RPC-level {@code preprocessor}.
*
* @param rpcPreprocessor the {@link HttpPreprocessor} that preprocesses an invocation
*/
public ClientPreprocessorsBuilder addRpc(RpcPreprocessor rpcPreprocessor) {
rpcPreprocessors.add(requireNonNull(rpcPreprocessor, "rpcPreprocessor"));
return this;
}

/**
* Returns a newly-created {@link ClientPreprocessors} based on the decorators added to this builder.
*/
public ClientPreprocessors build() {
return new ClientPreprocessors(preprocessors, rpcPreprocessors);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,8 @@ ClientRequestContext newDerivedContext(RequestId id, @Nullable HttpRequest req,
* Returns the {@link EndpointGroup} used for the current {@link Request}.
*
* @return the {@link EndpointGroup} if a user specified an {@link EndpointGroup} when initiating
* a {@link Request}. {@code null} if a user specified an {@link Endpoint}.
* a {@link Request}.
*/
@Nullable
EndpointGroup endpointGroup();

/**
Expand Down
Loading

0 comments on commit d72c4ce

Please sign in to comment.