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 request attributes in RestClient #32027

Closed
evkaky opened this issue Jan 14, 2024 · 14 comments
Closed

Introduce request attributes in RestClient #32027

evkaky opened this issue Jan 14, 2024 · 14 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@evkaky
Copy link

evkaky commented Jan 14, 2024

Hi there!
When spring boot 3.2 came out, I considered switching from WebClient to RestClient as the first one needs all interceptors to be written in reactive style which makes them harder to support and understand.
It turns out RestClient doesn't provide any alternates to 2 pretty important features which are present in WebClient and which we heavily use.

  1. no attribute(key, value) method for RestClient. That means it's not possible to pass a value from the call site into the interceptor code. Dozen of auth interceptors become impossible to implement with RestClient. Here is how we use them with WebClient:
myWebClient
    .get()
    .uri("/smth")
    .attribute("token" token)
    .retrieve()
    // ...

// the interceptor code
public ExchangeFilterFunction authenticate() {
    return (request, next) -> {
        String token = request.attribute("token").get().toString();
        // ...
    };
}
  1. No single option to configure timeouts. With WebClient there are lots of them. An example
@Bean
WebClientCustomizer webClientGlobalTimeoutCustomizer() {
    return (WebClient.Builder webClientBuilder) -> {
        ConnectionProvider provider = ConnectionProvider.builder("common")
            .maxIdleTime(Duration.ofSeconds(10))
            .maxLifeTime(Duration.ofSeconds(60))
            .pendingAcquireTimeout(Duration.ofSeconds(30))
            .evictInBackground(Duration.ofSeconds(60))
            .build();

        HttpClient httpClient = HttpClient
            .create(provider)
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeoutInMillis)
            .doOnConnected(conn -> conn
                .addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
                .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));

        webClientBuilder.clientConnector(new ReactorClientHttpConnector(httpClient));
    };
}

Are those things something which is planned to be implemented in spring boot 3.3+ ? Or there some fundamental issues with RestClient underlying implementation so no attribute() and no timeouts?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 14, 2024
@sbrannen sbrannen changed the title RestClient lack of functionality Lacking functionality in RestClient Jan 14, 2024
@sbrannen sbrannen added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Jan 14, 2024
@sbrannen
Copy link
Member

@ThomasKasene
Copy link

ThomasKasene commented Jan 14, 2024

Regarding the attribute feature: I'm attempting a PR towards spring-projects/spring-security#13588 and the solution I've come up with so far is calling RequestContextHolder.currentRequestAttributes() and then adding whatever attributes I need to the resulting RequestAttributes object. I don't actually know if this is the correct way of doing it or not, and I suspect I'll encounter major issues once I try to implement a version of the interceptor that doesn't have a servlet request context for me to use (the RestClient version of ServerOAuth2AuthorizedClientExchangeFilterFunction).

For both of these reasons it would be nice to have a uniform way of passing attributes to the interceptors, instead of every interceptor having to re-invent the wheel.

@poutsma
Copy link
Contributor

poutsma commented Jan 15, 2024

In the future, please do not list multiple feature requests in a single issue, but create separate issues instead. This makes it easier to track the state of each.

Attributes

With regard to attributes, it is important to understand that they were introduced into WebClient to solve a particular problem of reactive environments: the lack of dependable thread-locals. Since its introduction in Spring Framework 3.0, the non-reactive RestTemplate never needed attributes, or at least such a feature request was not made (so far).

The RestClient is based on the existing infrastructure of RestTemplate (i.e. ClientHttpRequestFactory, ClientHttpRequestInterceptor , etc.), so without any support for attributes. We initially considered adding them (some leftovers even made it into 6.1.0, see #31625), but in the end decided to drop attribute support for RestClient, because adding them would require making many changes in the existing infrastructure, while it was not yet clear if they were even needed. After all, RestTemplate had done fine so far without attributes!

We can of course consider introducing support for attributes in RestClient for Spring Framework 6.2, but I'd like to know if you still consider them essential, given the information above.

Timeouts

I am not entirely sure what you mean with the lack of options to configure timeouts. For RestClient (and RestTemplate), these are configured on the particular ClientHttpRequestFactory you decide to use. For instance, for the JettyClientHttpRequestFactory, there is a connection timeout and a read timeout. Other request factories have similar timeout properties you can configure.

Are these configuration options sufficient, or is there something that is lacking?

@poutsma poutsma added the status: waiting-for-feedback We need additional information before we can continue label Jan 15, 2024
@ThomasKasene
Copy link

We initially considered adding them (some leftovers even made it into 6.1.0, see #31625), but in the end decided to drop attribute support for RestClient, because adding them would require making many changes in the existing infrastructure, while it was not yet clear if they were even needed. After all, RestTemplate had done fine so far without attributes!

True enough, it's possible that some of us have become too preoccupied with keeping the APIs/usage of RestClient as close to that of WebClient as possible, without considering what's best in the long run. 😃At the moment, my most pressing need is being able to customize the Authorization header in a simple manner, on a per-request basis.

There are helper methods in the RestClient API for adding certain headers such as accept(MediaType...) - is there any particular reason why there isn't an authorization(Supplier<String>) method in there as well, apart from nobody having requested one yet? It would enable us to make something that looks pretty succinct:

restClient
    .get()
    .uri("/rest/person")
    .authorization(oauth2
        .clientRegistrationId("my-client-registration")
        .principal("johndoe"))
    .retrieve()
    // ...

as opposed to the monstrosity we'd have to write with today's API:

restClient
    .get()
    .uri("/rest/person")
    .headers(headers -> headers
        .setBearerAuth(oauth2
            .clientRegistrationId("my-client-registration")
            .principal("johndoe")
            .build()))
    .retrieve()
    // ...

Note the mention of "bearer" (which should be an implementation detail, not up to the caller) and an explicit call to some sort of terminating method (build() in this case).

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 16, 2024
@poutsma poutsma added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jan 17, 2024
@poutsma
Copy link
Contributor

poutsma commented Jan 17, 2024

Re-added feedback label as an answer from @evkaky is still forthcoming.

@evkaky
Copy link
Author

evkaky commented Jan 19, 2024

Hi everyone.
Thanks a lot for your support!
@poutsma , on timeouts - yeah, totally makes sense, I somehow missed that requestFactory method on rest client builder, when I was exploring the api.

On attribute() replacement for rest client - I still have doubts.
Is the approach Thomas suggested (with RequestContextHolder.currentRequestAttributes()) is an idiomatic way to dynamically pass data to interceptors in rest template / rest client world?
Is this how it should be used? (assuming I have to decouple rest client creation from the code where I actually make an http call. This rest client should be reusable across entire app in my case)

@Bean
RestClient myRestClient(RestClient.Builder builder) {
  builder
    .baseUrl("...")
    .requestInterceptor((request, body, execution) -> {
        String someId = RequestContextHolder.getRequestAttributes().getAttribute("someId", 0).toString();
        // I need this someId to make a request to a 3rd party API,
        // that 3rd party API will return a `token`, which I will attach to the header of the initial request, like this
        String token = getTokenFromAuthService(someId);
        request.getHeaders().setBearerAuth(token);
    })
    .build();
}

and then, somewhere on the call site (in my case "call site" is a message handler of some queue):

@Autowired
@Qualifier("myRestClient")
RestClient myRestClient;

void onDataReceive(String someId) {
   RequestContextHolder.getRequestAttributes().setAttribute("someId", someId, 0);
   myRestClient
     .get()
     .retrieve()
     // ...
}

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 19, 2024
@davebarda
Copy link

davebarda commented Jan 29, 2024

+1 for the request attributes, for my case, I want to transfer some data to the ClientRequestObservationConvention, but there's no way of adding some additional data to the request nor to the ClientRequestObservationContext as i've explained at #32153.
In my context, the http request might be sent from multiple contexts(kafka,rabbit,rest,grpc, etc..) and I don't feel like populating RequestContextHolder is the right way.

Regarding the question about RestTemplate, I can say that it was solved by wrapping all of our request and sending our own metrics instead of using the ones supplied by spring, due to lack of context. Now that we're investing time in using the new client, I hoped I could improve the experience and the robustness of the solution.

@evkaky
Copy link
Author

evkaky commented Jan 30, 2024

It seems like RequestContextHolder can be used only within servlet environment (spring-web-mvc). If you have some queue handler (like @davebarda has) RequestContextHolder.getRequestAttributes() seems to always return null, doesn't it?
If so, I would also vote for adding attribute method (or smth similar) to RestClient

@poutsma poutsma self-assigned this Feb 1, 2024
@poutsma poutsma changed the title Lacking functionality in RestClient Introduce request attributes in RestClient Feb 1, 2024
@poutsma poutsma added this to the 6.2.x milestone Feb 1, 2024
@poutsma poutsma removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Feb 1, 2024
@poutsma
Copy link
Contributor

poutsma commented Feb 1, 2024

Thanks for the feedback, everybody, I have scheduled this issue for 6.2.

@snicoll
Copy link
Member

snicoll commented Feb 12, 2024

When handling this issue, please take #32231 and #32232 into account as it will require an update.

@bclozel bclozel changed the title Introduce request attributes in RestClient Introduce request attributes in RestClient Feb 14, 2024
@poutsma poutsma removed this from the 6.2.x milestone Jun 11, 2024
@KrishnaST
Copy link

KrishnaST commented Jul 31, 2024

Use of ThreadLocals via RequestContextHolder is simply bad design. There is extra burdon of cleaning up after. +1 from me for request attributes.

@bclozel
Copy link
Member

bclozel commented Jul 31, 2024

@KrishnaST this is already done. Is there something missing? Please advise.

@KrishnaST
Copy link

@bclozel am I missing something here? How to pass attributes to interceptors?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

9 participants