Skip to content

GH-10830: Add RestClient support for HTTP outbound endpoints#10835

Open
mailaruns wants to merge 6 commits intospring-projects:mainfrom
mailaruns:GH-10830
Open

GH-10830: Add RestClient support for HTTP outbound endpoints#10835
mailaruns wants to merge 6 commits intospring-projects:mainfrom
mailaruns:GH-10830

Conversation

@mailaruns
Copy link

Fixes: gh-10830

* add `rest-client` XSD attribute for HTTP outbound gateway/channel adapter
* extend XML parsers to wire `RestClient` and reject mixed `rest-template` + `rest-client`
* add `HttpRequestExecutingMessageHandler` constructors and execution path for `RestClient`
* add DSL overloads in `Http` and `HttpMessageHandlerSpec` for preconfigured `RestClient`
* guard local client options (`request-factory`, `error-handler`, `message-converters`, `encoding-mode`) when external client is used
* add parser/DSL/handler tests, including invalid dual-client configuration cases
* update HTTP reference docs (`outbound`, `timeout`, `java-config`) for `RestClient` usage

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool!
I left some review, but really nothing critical.
Thank you!

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, run ./gradlew :spring-integration-http:check before pushing changes to the PR.
Thanks

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, rebase your branch to the latest main and run ./gradlew :spring-integration-http:check before pushing.
You have some Checkstyle violations:

Error: eckstyle] [ERROR] /home/runner/work/spring-integration/spring-integration/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java:207: Block tags have to appear in the order '[@param, @return, @throws, @since, @deprecated, @see]'. [AtclauseOrder]
Error: eckstyle] [ERROR] /home/runner/work/spring-integration/spring-integration/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java:222: Block tags have to appear in the order '[@param, @return, @throws, @since, @deprecated, @see]'. [AtclauseOrder]
Error: eckstyle] [ERROR] /home/runner/work/spring-integration/spring-integration/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java:236: Block tags have to appear in the order '[@param, @return, @throws, @since, @deprecated, @see]'. [AtclauseOrder]

@mailaruns
Copy link
Author

Addressed in the latest commit.

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your change also deserves a [[x7.1-http-changes]] entry in the whats-new.adoc.

Thanks

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still missed whats-new.adoc entry.
And ultimate goal is to not have deprecation warnings at all.

public static HttpMessageHandlerSpec outboundChannelAdapter(URI uri, @Nullable RestClient restClient) {
return (restClient != null
? new HttpMessageHandlerSpec(uri, restClient).expectReply(false)
: outboundChannelAdapter(uri, (RestTemplate) null));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not RestClient-based delegation?
I believe that way we would avoid one more deprecation warning.

public static HttpMessageHandlerSpec outboundChannelAdapter(String uri, @Nullable RestClient restClient) {
return (restClient != null
? new HttpMessageHandlerSpec(uri, restClient).expectReply(false)
: outboundChannelAdapter(uri, (RestTemplate) null));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DITTO

public static HttpMessageHandlerSpec outboundChannelAdapter(Expression uriExpression, @Nullable RestClient restClient) {
return (restClient != null
? new HttpMessageHandlerSpec(uriExpression, restClient).expectReply(false)
: outboundChannelAdapter(uriExpression, (RestTemplate) null));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DITTO

public static HttpMessageHandlerSpec outboundGateway(URI uri, @Nullable RestClient restClient) {
return (restClient != null
? new HttpMessageHandlerSpec(uri, restClient)
: outboundGateway(uri, (RestTemplate) null));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here, too

public static HttpMessageHandlerSpec outboundGateway(String uri, @Nullable RestClient restClient) {
return (restClient != null
? new HttpMessageHandlerSpec(uri, restClient)
: outboundGateway(uri, (RestTemplate) null));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

RestClient.Builder localRestClientBuilder = this.localRestClientBuilder;
Assert.state(localRestClientBuilder != null, "'localRestClientBuilder' must not be null");
localRestClientBuilder.defaultStatusHandler(errorHandler);
rebuildLocalRestClient();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to rebuild client every time?
The pattern in Spring Beans is:

  1. Create an instance
  2. Provide properties
  3. initialize

And that is exactly what happens with our doInit().
If some tests are failing, then they have to be fixed to call afterPropertiesSet().

public HttpHeaders getHeaders() {
return headers;
}
private final Map<String, Object> attributes = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is off with indents in this change.
However, I wonder if the change to this test class is legit for your current work.
Maybe this has to be fixed separately?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attributes map is now mandatory.
Previously, it was set to null, which caused the CookieTests to fail with a NullPointerException under the current RestClient-based execution path.
This behavior occurs because the RestClient implementation requires the request attributes to be non-null.

Please advise.

PropertyAccessor dfa = new DirectFieldAccessor(this.handler);
dfa.setPropertyValue("localRestClientBuilder", null);
dfa.setPropertyValue("restClient", null);
dfa.setPropertyValue("restTemplate", template);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, revise your indents configuration.
We use tabs

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, rebase to the latest main.
Those failures in other modules should go away.
Also, whats-new.adoc might not merge clean, so keep a pulse on it when rebasing.

Thanks

* @deprecated Since 7.1 in favor of {@link RestClient}-based configuration.
*/
@Deprecated(since = "7.1", forRemoval = true)
@SuppressWarnings("removal")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... That as my thinking, too.
So, can we instead of this @SuppressWarnings("removal") delegate to the RestClient API instead of calling new HttpMessageHandlerSpec(uri, restTemplate) directly?

* @deprecated Since 7.1 in favor of {@link RestClient}-based configuration.
*/
@Deprecated(since = "7.1", forRemoval = true)
@SuppressWarnings("removal")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DITTO.

* @deprecated Since 7.1 in favor of {@link RestClient}-based configuration.
*/
@Deprecated(since = "7.1", forRemoval = true)
@SuppressWarnings("removal")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. S, revise all these @SuppressWarnings("removal"), please, in favor of RestClient.create(RestTemplate) and resepctive factory delegation.
What I mean specifically: let's try to not call deprecated API from our code as much as possible!

this(new LiteralExpression(uri));
}

protected HttpMessageHandlerSpec(Expression uriExpression) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to have these constructors if we could just follow same @Nullable pattern for new RestClient ctors?
And probably that way we would avoid some if..else in the Http factory.

private final RestTemplate restTemplate;

@Nullable
private volatile RestClient restClient;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, consider to put @Nullable next to the type it is declared for.
It might work on the whole property yet, but our style and consistency dictates us that it has to just next to the type:

private volatile @Nullable RestClient restClient;

@SuppressWarnings("removal")
public HttpRequestExecutingMessageHandler(String uri) {
this(uri, null);
this(uri, (RestTemplate) null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.
I know you said that many of our tests rely on the RestTemplate as a default environment.
But looking into this, it turns out to be a default behavior for everyone.
We might migrate this to the RestClient, but whoever uses this class with such a default ctor, would continue blindly exchange with a RestTemplate.

I wonder if we really should put an effort and migrate those our tests to the RestClient and have this class relying on the RestClient by default.

If we are going to remove RestTemplate eventually anyway, it would be better to have tests based on the RestClient instead of just removing whatever could bring extra coverage benefit.

public void restClientConfig() {
HttpRequestExecutingMessageHandler handler =
(HttpRequestExecutingMessageHandler) this.restClientConfig.getHandler();
assertThat(TestUtils.<RestClient>getPropertyValue(handler, "restClient")).isEqualTo(this.customRestClient);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is better to use isSameAs().
We also can use here assertThatObject() instead of that generic argument: doesn't look like we need here exact type of that property.

public void restClientDslFactoryMethods() {
RestClient restClient = RestClient.create();
assertThat(Http.outboundGateway("http://localhost/test", restClient)).isNotNull();
assertThat(Http.outboundChannelAdapter("http://localhost/test", restClient)).isNotNull();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this test makes sense at all.
Those factory methods really don't return null.
It is probably better to have a test which would check for exception thrown from those factories if they could.
Otherwise, please, remove this as redundant.


The HTTP outbound Java DSL factory methods with nullable `RestClient` now delegate through RestClient-based configuration to avoid deprecated `RestTemplate` fallback paths.
Also, `HttpRequestExecutingMessageHandler` applies local `RestClient.Builder` options during bean initialization (`afterPropertiesSet()`/`doInit()`), following the standard Spring bean lifecycle pattern.
See xref:http/outbound.adoc[] and xref:http/java-config.adoc[] for more information.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's too much info in the What's New.
Please, keep it brief like:

The `HttpRequestExecutingMessageHandler` (its XML and Java DSL) now can be used with a `RestClient`.
The `RestTemplate` configuration is deprecated.
See xref:http.adoc[] for more information.

And move this section to the end of this file.

Copy link
Member

@artembilan artembilan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is strange to have all my main changes in the PR.
Looks like you have done merge, but not rebase.
Please, try to fix your branch: https://stackoverflow.com/questions/804115/when-do-you-use-git-rebase-instead-of-git-merge

…points

    Fixes: spring-projectsgh-10830

    * add `rest-client` XSD attribute for HTTP outbound gateway/channel adapter
    * extend XML parsers to wire `RestClient` and reject mixed `rest-template` + `rest-client`
    * add `HttpRequestExecutingMessageHandler` constructors and execution path for `RestClient`
    * add DSL overloads in `Http` and `HttpMessageHandlerSpec` for preconfigured `RestClient`
    * guard local client options (`request-factory`, `error-handler`, `message-converters`, `encoding-mode`) when external client is used
    * add parser/DSL/handler tests, including invalid dual-client configuration cases
    * update HTTP reference docs (`outbound`, `timeout`, `java-config`) for `RestClient` usage

Signed-off-by: Arun Sethumadhavan <mailaruns@gmail.com>
…n and align with RestClient-first APIs

Fixes: spring-projectsgh-10830

* deprecate RestTemplate-based constructors and mutators in `HttpRequestExecutingMessageHandler` (`since = "7.1", forRemoval = true`)
* keep current runtime behavior stable in the handler (RestTemplate local path + RestClient external path) to avoid backward-compatibility regressions
* deprecate RestTemplate-based DSL factory methods in `Http` and align `RestClient` methods to `7.1` documentation
* rework `HttpMessageHandlerSpec` to use `RestClient.create(restTemplate)` for RestTemplate-based construction and simplify client state tracking
* deprecate RestTemplate-focused spec mutators (`requestFactory`, `errorHandler`, `messageConverters`) in favor of configuring `RestClient`
* update HTTP reference docs (`java-config`, `outbound`, `timeout`) with 7.1 RestClient guidance and RestTemplate deprecation notes
* adjust related tests/imports to match deprecation and client-selection changes

Signed-off-by: Arun Sethumadhavan <mailaruns@gmail.com>
Signed-off-by: Arun Sethumadhavan <mailaruns@gmail.com>
…on with RestClient.Builder

Fixes: spring-projectsgh-10830

* keep no-client `HttpRequestExecutingMessageHandler` constructors non-deprecated and use `@SuppressWarnings("removal")` where they delegate to deprecated RestTemplate constructors
* switch local (no external client) setup to `RestClient.Builder` and build the local `RestClient` in `doInit()`
* keep local mutator APIs (`setRequestFactory`, `setErrorHandler`, `setMessageConverters`) and apply them to the local `RestClient.Builder`
* rebuild the local `RestClient` after local mutator updates so post-init changes are visible at runtime
* retain guards that reject local-only options when an external RestTemplate/RestClient is provided
* handle `Void` response type in RestClient exchange path via `toBodilessEntity()` restoring prior behavior for bodiless replies.
* make `restClient` `volatile` because it is rebuilt and read across lifecycle/request threads
* remove deprecations from matching local mutators in `HttpMessageHandlerSpec`
* update outbound/parser/DSL tests to align with local RestClient-backed behavior
* adjust proxy and cookie test fixtures for RestClient path (including non-null request attributes in cookie request stub)

Signed-off-by: Arun Sethumadhavan <mailaruns@gmail.com>
…client lifecycle

Fixes spring-projectsgh-10830

* refactor nullable `RestClient` HTTP DSL factory methods to delegate via RestClient-first paths and remove duplicate fallback branches
* add shared `outboundGatewaySpec(...)` helpers in `Http` to centralize URI/String/Expression client selection
* add non-deprecated local constructors in `HttpMessageHandlerSpec` for URI/String/Expression paths
* keep deprecated RestTemplate DSL overloads and add `@SuppressWarnings("removal")` where they intentionally call deprecated constructors
* build local `RestClient` once in `HttpRequestExecutingMessageHandler#doInit()` and stop rebuilding on each local mutator call
* keep local mutators (`setRequestFactory`, `setErrorHandler`, `setMessageConverters`) applying to local `RestClient.Builder`
* align HTTP proxy test flow with lifecycle semantics by re-initializing after mutator-based setup
* revise HTTP proxy/cookie test fixtures and formatting updates for the current RestClient-backed local behavior
* add `[[x7.1-http-changes]]` section to `whats-new.adoc` documenting HTTP 7.1 updates

Signed-off-by: Arun Sethumadhavan <mailaruns@gmail.com>
Fixes: spring-projectsgh-10830

* route deprecated RestTemplate HTTP DSL/spec paths through RestClient.create(RestTemplate)
* remove deprecated internal-call suppressions in Http factory methods and simplify nullable RestClient delegation
* align HttpRequestExecutingMessageHandler default constructor flow with RestClient-first initialization
* switch local message converter setup to non-deprecated RestClient.Builder.configureMessageConverters API
* update HTTP parser/DSL tests and reduce deprecated-constructor warnings in outbound handler tests
* What's New entry for HTTP RestClient/RestTemplate migration notes

Signed-off-by: Arun Sethumadhavan <mailaruns@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants