Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Preprocessor #6057

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.");
}
}
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) {
ikhoon marked this conversation as resolved.
Show resolved Hide resolved
return (ClientBuilder) super.preprocessor(decorator);
}

@Override
public ClientBuilder rpcPreprocessor(RpcPreprocessor decorator) {
return (ClientBuilder) super.rpcPreprocessor(decorator);
}
}
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,142 @@
/*
* 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();
}

static ClientPreprocessorsBuilder builder() {
ikhoon marked this conversation as resolved.
Show resolved Hide resolved
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,71 @@
/*
* 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 {

ikhoon marked this conversation as resolved.
Show resolved Hide resolved
private final List<HttpPreprocessor> preprocessors = new ArrayList<>();
private final List<RpcPreprocessor> rpcPreprocessors = new ArrayList<>();

/**
* 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
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ public ClientRequestContext build() {
responseCancellationScheduler = CancellationScheduler.ofClient(0);
}
final DefaultClientRequestContext ctx = new DefaultClientRequestContext(
eventLoop(), meterRegistry(), sessionProtocol(), id(), method(), requestTarget(), options,
eventLoop(), meterRegistry(), sessionProtocol(), id(), method(), requestTarget(),
endpointGroup, options,
request(), rpcRequest(), requestOptions, responseCancellationScheduler,
isRequestStartTimeSet() ? requestStartTimeNanos() : System.nanoTime(),
isRequestStartTimeSet() ? requestStartTimeMicros() : SystemInfo.currentTimeMicros());

ctx.init(endpointGroup).handle((unused, cause) -> {
ctx.init().handle((unused, cause) -> {
ctx.finishInitialization(cause == null);
if (!timedOut()) {
ctx.responseCancellationScheduler().initAndStart(ctx.eventLoop(), noopCancellationTask);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public ClientRequestContext newDerivedContext(RequestId id, @Nullable HttpReques
return unwrap().newDerivedContext(id, req, rpcReq, endpoint);
}

@Nullable
@Override
public EndpointGroup endpointGroup() {
return unwrap().endpointGroup();
Expand Down
Loading
Loading