Customizing ClientHttpResponse when using Spring WebFlux

Few days back, I was working on integrating an external service API call in my application. I was using the new Spring WebFlux reactive framework.

This external service API accepted XML request and responded in XML as well. This was a pretty common use case of API call integration with the uncommon part being WebFlux framework.

When I finished coding it and tested that integration, I realized that it was not that common use case after all. The call was failing, and logs indicated that Spring framework was trying to find a codec for application/octet-stream. But the service was returning an XML in the response..so why was Spring trying to find a codec for application/octet-stream..

After some debugging, I found that the external service didn’t have a Content-Type header in response. And Spring framework was using application/octet-stream as fallback value.

Since, this was an external service, I could only request them to add Content-type header and wait for it or to make things work, I could modify incoming response to add content-type header before Spring would search for a suitable codec. To achieve that with Spring Webflux, I implemented a decorator of class ClientHttpResponse.

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Flux;

public class CustomResponseWrapper implements ClientHttpResponse {

private final ClientHttpResponse delegate;

public CustomResponseWrapper(ClientHttpResponse delegate) {
this.delegate = delegate;
}

@Override
public HttpStatus getStatusCode() {
return delegate.getStatusCode();
}

@Override
public int getRawStatusCode() {
return delegate.getRawStatusCode();
}

@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return delegate.getCookies();
}

@Override
public Flux<DataBuffer> getBody() {
return delegate.getBody();
}

@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
final HttpHeaders delegateHeaders = delegate.getHeaders();
headers.addAll(delegateHeaders);
if (delegateHeaders.getContentType() == null) {
headers.setContentType(MediaType.APPLICATION_XML);
}
return headers;
}
}

and extended class ReactorClientHttpConnector to decorate ClientHttpResponse object with CustomResponseWrapper

import java.net.URI;
import java.util.function.Function;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;

public class CustomReactorClientHttpConnector extends ReactorClientHttpConnector {

public CustomReactorClientHttpConnector(HttpClient httpClient) {
super(httpClient);
}

@Override
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
return super.connect(method, uri, requestCallback).map(CustomResponseWrapper::new);
}
}

By doing this, I was able to modify the external service response to add content-type header by extending functionality in Spring Webflux framework and it used the codec for XML to deserialize response body.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.