Skip to content

WebClient's ResponseSpec should provide access to response headers #22368

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

Closed
csabakos opened this issue Feb 6, 2019 · 4 comments
Closed

WebClient's ResponseSpec should provide access to response headers #22368

csabakos opened this issue Feb 6, 2019 · 4 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@csabakos
Copy link

csabakos commented Feb 6, 2019

Affects: 5.1.4


This in an enhancement request for WebClient.

Currently there are two ways to process the response with WebClient:

  1. WebClient#exchange() gives full access to the ClientResponse (including the HTTP response headers), but it requires one to handle erroneous HTTP status codes manually.
  2. WebClient#retrieve() simplifies error handling, but prevents access to ClientResponse.

I find myself using retrieve() almost exclusively, but sometimes I have to resort to exchange() (and doing my own error handling) due to the limitation described above.

It would be really nice for WebClient#retrieve() to provide access to ClientResponse (or at least the HTTP response headers) while still taking care of the bad status code handling.

An example use case of this is extracting a value from a HTTP header or a cookie in addition to deserializing the body to an object.

A potential implementation of this could be to add Mono<ClientResponse> clientResponse(); to WebClient.ResponseSpec. DefaultResponseSpec would call .cache() on the responseMono in the constructor, and return that from the new clientResponse() method.

That would allow one to write something like:

WebClient.ResponseSpec responseSpec = webClient.get().uri("http://...").retrieve();
responseSpec.bodyToMono(MyObject.class)
    .zipWith(responseSpec.clientResponse())
    .map((body, clientResponse) -> {
        // Access clientResponse.headers(), etc.
    }
    ...

An alternative solution could be to simply make DefaultResponseSpec public, so that one could do this manually:

Mono<ClientResponse> clientResponseMono = webClient.get().uri("http://...").exchange().cache();
new DefaultResponseSpec(clientResponseMono)
    .bodyToMono(MyObject.class)
    .zipWith(clientResponseMono)
    .map((body, clientResponse) -> {
        // Access clientResponse.headers(), etc.
    }
    ...
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Feb 6, 2019
@rstoyanchev
Copy link
Contributor

Caching the response isn't an option, and it wouldn't cover the Flux case either. Having to break the fluent chain of calls with a WebClient.ResponseSpec local var isn't ideal either. Making WebClient.DefaultResponseSpec public has the same issue.

@poutsma we considered bodyToMonoEntity and bodyToFluxEntity methods on ResponseSpec at some point. Was there some technical reason it couldn't be done?

@poutsma
Copy link
Contributor

poutsma commented Mar 12, 2019

At some point, we moved the toEntity methods from ResponseSpec to ClientResponse because that made more sense: both client response and response entity represent the entire HTTP message (status, headers and body), while response spec focusses on the body only. I still think this was a good decision.

But we could expose the response headers in WebClient.ResponseSpec by adding something like:

<T,R> Mono<R> bodyMonoWithHeaders(Class<T> bodyType, BiFunction<HttpHeaders, Mono<T>, Mono<R>> function);
<T,R> Flux<R> bodyFluxWithHeaders(Class<T> bodyType, BiFunction<HttpHeaders, Flux<T>, Flux<R>> function);

I am not sure about the names yet, but the idea should be clear: you get access to both headers and body mono/flux in a function. We might even consider exposing the status code as well.

This would allow you to do the following:

Mono<String> result = this.webClient.get()
	.uri("/greeting")
	.retrieve()
	.bodyMonoWithHeaders(String.class, (headers, body) -> body.map(s -> s + " " + headers.getContentType()));

Thoughts?

@poutsma poutsma self-assigned this Mar 12, 2019
@poutsma poutsma added this to the 5.2 M1 milestone Mar 12, 2019
@rstoyanchev rstoyanchev added in: web Issues in web modules (web, webmvc, webflux, websocket) and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Mar 12, 2019
@csabakos
Copy link
Author

@poutsma that makes sense to me and would cover my use case perfectly.

The only thing that it might not cover is easy access to cookies. I suppose one could always parse cookies from the headers, but ClientResponse#cookies() is a lot more convenient.

@poutsma
Copy link
Contributor

poutsma commented Jul 18, 2019

Despite my earlier comment, I decided to go with duplicating toEntity* on the ResponseSpec.

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

5 participants