-
Notifications
You must be signed in to change notification settings - Fork 926
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
based clients
#6060
base: main
Are you sure you want to change the base?
Conversation
64371bc
to
e111262
Compare
e111262
to
23a2685
Compare
ready for review |
PROXY("proxy", false, false, 0); | ||
PROXY("proxy", false, false, 0), | ||
|
||
UNDEFINED("undefined", false, false, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) What do you think of using HTTP
as the default session protocol instead? If a scheme is missing in curl or a browser, HTTP protocol is used by default.
$ curl google.com -v
* Host google.com:80 was resolved.
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Within Armeria, HTTP is currently not the default session protocol.
e.g. the following API exists
WebClient of(SessionProtocol protocol, EndpointGroup endpointGroup)
but the following API doesn't
WebClient of(EndpointGroup endpointGroup)
I'm fine with setting HTTP
as the default session protocol, but for consistency I prefer that all APIs reflect this default session protocol behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WebClient of(EndpointGroup endpointGroup)
It would also be a good idea to add this API in the future.
SessionProtocol.UNDEFINED
is unlikely to be used by users, and additional validation is required in places where UNDEFINED
is not allowed.
Personally, I doubt that SessionProtocol.PROXY
was a good decision. Because the usage of SessionProtocol.PROXY
is limited only when specifying a port for creating a server. The rest of our codebase has a lot of validation added to check if it is HTTP
or HTTPS
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would also be a good idea to add this API in the future.
If the others agree with this direction, let me also add the relevant APIs in this PR then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also prefer not using SessionProtocol.UNDEFINED
if we can avoid it. 😉
/** | ||
* Allows creation of a new {@link ClientBuilderParams} based on a previous {@link ClientBuilderParams}. | ||
*/ | ||
public final class ClientBuilderParamsBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Could we use this builder to create DefaultClientBuilderParams
in ClientBuilderParams.of()
?
import com.linecorp.armeria.common.annotation.Nullable; | ||
import com.linecorp.armeria.common.util.UnmodifiableFuture; | ||
|
||
public final class FailingEndpointGroup implements EndpointGroup { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional) How about exposing FailingEndpointGroup as a public API or its singleton instance? It may be useful for a user to check whether the current EndpointGroup
is initialized in Preprocessor
to conditionally create an EndpointGroup
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UninitializedEndpointGroup
or UndefinedEndpointGroup
?
How about raising the exception for all method calls?
private static final String PREPROCESSOR_PREFIX = "armeria-preprocessor-"; | ||
public static final String ENDPOINTGROUP_PREFIX = "armeria-group-"; | ||
public static final String UNDEFINED_URI_AUTHORITY = "armeria-undefined:1"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) What do you think about also using UNDEFINED_URI_AUTHORITY
as a placeholder for Preprocessor
to indicate that the endpoint needs initializing?
If SerializationFormat
is needed, I wonder if it would also be possible to express it like this:
gproto+http://armeria-undefined:1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understood the question. Are you suggesting that we treat UNDEFINED_URI_AUTHORITY
and Preprocessor
equivalently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering if it was possible to reuse the existing undefined URI, UNDEFINED_URI_AUTHORITY
rather than creating a new one.
* Returns a {@link ClientBuilderParamsBuilder} which allows creation of a new | ||
* {@link ClientBuilderParams} based on the current properties. | ||
*/ | ||
default ClientBuilderParamsBuilder paramsBuilder() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Global comment: @UnstableApi
* Sets the {@link SerializationFormat} for this {@link ClientBuilderParams}. | ||
*/ | ||
public ClientBuilderParamsBuilder serializationFormat(SerializationFormat serializationFormat) { | ||
this.serializationFormat = serializationFormat; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rnn for all methods?
* the specified {@link SerializationFormat} or | ||
* {@link ClientPreprocessors} | ||
*/ | ||
public static <T> T newClient(ClientPreprocessors preprocessors, Class<T> clientType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static <T> T newClient(ClientPreprocessors preprocessors, Class<T> clientType) { | |
public static <T> T newClient(ClientPreprocessors preprocessors, Class<T> clientType) { |
* @param clientType the type of the new client | ||
* | ||
* @throws IllegalArgumentException if the specified {@code clientType} is unsupported for | ||
* the specified {@link SerializationFormat} or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SerializationFormat is not specfied
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good all in all. 👍
return uri == UNDEFINED_URI; | ||
return (uri == ClientUtil.UNDEFINED_URI || | ||
ClientBuilderParamsUtil.UNDEFINED_URI_AUTHORITY.equals(uri.getAuthority())) && | ||
uri.getPort() == 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably remove uri.getPort() == 1
because the above conditions already imply it.
import com.linecorp.armeria.common.annotation.Nullable; | ||
import com.linecorp.armeria.common.util.UnmodifiableFuture; | ||
|
||
public final class FailingEndpointGroup implements EndpointGroup { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UninitializedEndpointGroup
or UndefinedEndpointGroup
?
How about raising the exception for all method calls?
@@ -89,7 +94,7 @@ final class DefaultClientBuilderParams implements ClientBuilderParams { | |||
normalizedAbsolutePathRef); | |||
} else { | |||
// Create a valid URI which will never succeed. | |||
uri = URI.create(schemeStr + "://armeria-group-" + | |||
uri = URI.create(schemeStr + "://" + ClientBuilderParamsUtil.ENDPOINTGROUP_PREFIX + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: ENDPOINT_GROUP_PREFIX
"Scheme and authority must be specified in \":path\" or " + | ||
"in \":scheme\" and \":authority\". :path=" + | ||
originalPath + ", :scheme=" + req.scheme() + ", :authority=" + req.authority())); | ||
"Failed to parse a scheme: " + reqTarget.scheme(), e)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Failed to parse a scheme: " + reqTarget.scheme(), e)); | |
"Failed to parse a scheme: " + scheme, e)); |
if (scheme.serializationFormat() == SerializationFormat.NONE) { | ||
schemeStr = scheme.sessionProtocol().uriText(); | ||
} else { | ||
schemeStr = scheme.uriText(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (scheme.serializationFormat() == SerializationFormat.NONE) { | |
schemeStr = scheme.sessionProtocol().uriText(); | |
} else { | |
schemeStr = scheme.uriText(); | |
} | |
if (scheme.serializationFormat() == SerializationFormat.NONE) { | |
schemeStr = scheme.sessionProtocol().uriText(); | |
} else { | |
schemeStr = scheme.uriText(); | |
} |
I've seen this logic in multiple places. How about extracting it into a method? e.g. scheme.effectiveUriText()
PROXY("proxy", false, false, 0); | ||
PROXY("proxy", false, false, 0), | ||
|
||
UNDEFINED("undefined", false, false, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also prefer not using SessionProtocol.UNDEFINED
if we can avoid it. 😉
@UnstableApi | ||
public static GrpcClientBuilder builder(HttpPreprocessor httpPreprocessor) { | ||
requireNonNull(httpPreprocessor, "httpPreprocessor"); | ||
return builder(SerializationFormat.NONE, httpPreprocessor); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be GrpcSerializationFormats.PROTO
?
*/ | ||
@UnstableApi | ||
public static ThriftClientBuilder builder(RpcPreprocessor rpcPreprocessor) { | ||
return new ThriftClientBuilder(SerializationFormat.NONE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isnt' this ThriftSerializationFormats.BINARY
?
Motivation:
#6057 has described preprocessors, which allow users to dynamically modify how to connect to a target endpoint dynamically. However, there is a limitation where an
EndpointGroup
andSessionProtocol
must be provided even though thePreprocessor
will solely determine how to connect to a remote.For example, specifying the protocol and endpoint when building the client is not necessary since the preprocessor will fill in all of the necessary information.
I propose that users are able to build a client solely based on
Preprocessor
. By doing so, we may later introduce aXdsPreprocessor
which may dynamically determine how to connect to a remote based on resources.In order to support the above, I propose the following:
Preprocessor
, theSessionProtocol
must beUNDEFINED
.GrpcClients.of(HttpPreprocessor).params().scheme().sessionProtocol()
will beUNDEFINED
SessionProtocol.UNDEFINED
, at least onePreprocessor
must be present inClientOptions
.WebClient.of()
. Because it is easy to supply a targetEndpointGroup
,SessionProtocol
from its API, a preprocessor is not necessarily for creating a default WebClient.Modifications:
Preprocessor
.Preprocessor
, and optionally a SerializationFormat and prefix path.With the addition preprocessor-based clients, it is important to validate whether a client was created incorrectly. In order to validate client parameters from a unified location, it is necessary to know how a client was created. For this reason, following changes have been made to ClientBuilderParams:
armeria-group-
, preprocessor based clients will have prefixarmeria-preprocessor-
, etc...ClientBuilderParamsUtil
has utility methods which determines how a client has been created.ClientBuilderParamsBuilder
which introduces a fluent way to create a newClientBuilderParams
based on an existingClientBuilderParams
. This will help preserve the URI authority which embeds how a client was created.Result:
Preprocessor
Miscellaneous
Just because an exception has been thrown from preprocessing does not necessarily mean that the request has not been sent. I have removed the
UnprocessedRequestException
wrapping behavior.e.g.