1

I have to call remote REST endpoint from my web application build with Spring Boot 2.0.5

I can use HttpURLConnection, though as Spring have RestTemplate I checked what is it and I found it will be deprecated soon:

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

Also this page mentions the new class to call over HTTP. It is possible to use it in both synchronous and asynchronous ways:

WebClient offers a modern alternative to the RestTemplate with efficient support for both sync and async, as well as streaming scenarios

Problem is I not see in javadoc for WebClient any note about synchronous way it works:

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html

One more issue with WebClient - to get it works I need to have WebFlux in class path https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-webclient.html

But this will brake my Spring Security configuration as it build in synchronous way. As I understand once I have WebFlux Spring Security will use asynchronous configuration.

How can I make http calls to remote endpoint with Spring, or should I avoid and use HttpURLConnection class (or Apache's library)?

Update

WebFlux seems not cause any problems with Spring Security buit in synchronous way.

Also please note my application is not reactive - it is multi threaded (Sorry if I was not clear on this before). I have transactions so reactive approach seems not fit to my case.

P_M
  • 2,723
  • 4
  • 29
  • 62
  • You can still use `RestTemplate` also using the `WebClient` does indeed require web flux but that doesn't mean your whole application requires to be reactive. You can set the type of application to SERVLET and it will still use the regular mechanisms. – M. Deinum Jan 08 '19 at 06:47
  • I could use RestTemplate, but what is the sense as Spring team focus on WebClient. RestTemplate will be deprecated. I tried to go with WebClient. It seems I was mistaken about WebFlux - it not causes traditional (non-reactive) application problems, but WebClient.Builder is not thread safe, and I not sure how to use it properly. here my new question about it https://stackoverflow.com/questions/54136085/spring-boot-webclient-builder-bean-usage-in-traditional-servlet-multi-threaded-a – P_M Jan 10 '19 at 20:08
  • You should be using the `Builder` once to create an instance of `WebClient` and reuse that. But Spring Boot already configures a `WebClient` for you so why do you need a newly configured one?! – M. Deinum Jan 11 '19 at 10:34

2 Answers2

1

You can use Spring org.springframework.web.client.AsyncRestTemplate for async Rest call. Below is one of the Utility I have used for Sync and Async call. Below are the Rest utility and CallBack for Async.

/**
 *
 */
package com.debopam.services.policyenquiryservice.rest.util;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.client.AsyncRestTemplate;

/**
 * @author dpoddar
 *
 */
public class RestUtil {

    private String url;
    private HttpMethod httpMethod;
    private MultiValueMap<String, String> headers;
    private Map<String, Object> params;
    private Class<?> responseType;
    private List<Object> uriVariables;

    private HttpEntity<Object> httpEntity;

    //AsyncRestTemplate asyncRestTemplate = (AsyncRestTemplate) ContextProvider.getBean("customAsyncRestTemplate");

    /**
     * @param url
     * @param httpMethod
     * @param headers
     * @param params
     * @param responseType
     * @param uriVariables
     */
    public RestUtil(String url, HttpMethod httpMethod, MultiValueMap<String, String> headers,
            Map<String, Object> params, Class<?> responseType, List<Object> uriVariables) {
        super();
        this.url = url;
        this.httpMethod = httpMethod;
        this.headers = headers;
        this.params = params;
        this.responseType = responseType;
        this.uriVariables = uriVariables;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Foo callServicesync(RestTemplate restTemplate) {

        //DO a sync Call
        HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
        Foo foo = restTemplate.postForObject(fooResourceUrl, request, Foo.class);

    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void callServiceAsync(AsyncRestTemplate asyncRestTemplate,ResponseCallBack responseCallBack) {

        if(asyncRestTemplate.getMessageConverters().isEmpty()){
            asyncRestTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
        }

        ListenableFuture restCall = null;
        if(null != uriVariables){
            restCall = asyncRestTemplate.exchange(this.url, this.httpMethod, this.httpEntity, responseType,uriVariables);
        }else{
            restCall = asyncRestTemplate.exchange(this.url, this.httpMethod, this.httpEntity, responseType);
        }

        restCall.addCallback(responseCallBack);

    }

    public static class RestUtilBuilder {
        private String url;
        private HttpMethod httpMethod;
        private MultiValueMap<String, String> headers;
        private Map<String, Object> params;
        private Class<?> responseType;
        private List<Object> uriVariables;

        public RestUtilBuilder url(String url) {
            this.url = url;
            return this;
        }

        public RestUtilBuilder httpMethod(HttpMethod httpMethod) {
            this.httpMethod = httpMethod;
            return this;
        }

        public RestUtilBuilder headers(MultiValueMap<String, String> headers) {
            this.headers = headers;
            return this;
        }

        public RestUtilBuilder addHeader(String key,String value) {
            if(null == this.headers){
                this.headers = new LinkedMultiValueMap<>();
            }
            this.headers.add(key, value);
            return this;
        }

        public RestUtilBuilder params(Map<String, Object> params) {
            this.params = params;
            return this;
        }

        public RestUtilBuilder addparam(String key,Object value) {
            if(null == this.params){
                this.params = new HashMap<>();
            }
            this.params.put(key, value);
            return this;
        }

        public RestUtilBuilder responseType(Class<?> responseType) {
            this.responseType = responseType;
            return this;
        }

        public RestUtilBuilder uriVariables(List<Object> uriVariables) {
            this.uriVariables = uriVariables;
            return this;
        }

        public RestUtil build() {
            RestUtil util = new RestUtil(url, httpMethod, headers, params, responseType, uriVariables);
            util.httpEntity = new HttpEntity<Object>(util.params, util.headers);
            return util;
        }
    }

    public static RestUtilBuilder restUtil() {
        return new RestUtilBuilder();
    }

}



package com.debopam.services.policyenquiryservice.rest.util;

import java.util.Map;
import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.http.ResponseEntity;
import org.springframework.util.concurrent.ListenableFutureCallback;

/**
Response Call back for Async Call
*/
public abstract class ResponseCallBack<T> implements ListenableFutureCallback<ResponseEntity<T>>{

    private static final Logger logger = LoggerFactory.getLogger(ResponseCallBack.class.getName());

    Map<String,Object> inputs;


    public ResponseCallBack(Map<String,Object> inputs){
        this.inputs = inputs;
    }

    @Override
    public void onSuccess(ResponseEntity<T> stringResponseEntity) {
        onCallSuccess(this.inputs,stringResponseEntity);
    }

    @Override
    public void onFailure(Throwable ex) {
        logger.error(ex.getMessage(),ex);
        onCallFailure(this.inputs, ex);
    }

    //Do your stuff
    public abstract void onCallSuccess(Map<String,Object> inputs,ResponseEntity<T> stringResponseEntity);
    public abstract void onCallFailure(Map<String,Object> inputs,Throwable ex);
}

//Example
private void createRestUtilForAsync()
    {

    RestUtil restUtil = RestUtil.restUtil().url(url).addHeader("Accept", "application/json").addHeader("Content-Type", "application/json").addparam("xxx", 10).addparam("yyyy", "").addparam("zzz", "dsadsa").httpMethod(HttpMethod.POST).responseType(Policy.class).build();
    //create inputs
    ResponseCallBack<Policy> responseCallBack = new ResponseContractValuesCallBack(inputs);

    //asyncRestTemplate is autowired in the class
    restUtil.callServiceAsync(this.asyncRestTemplate, responseCallBack);
}

private void createRestUtilForSync()
    {

    RestUtil restUtil = RestUtil.restUtil().url(url).addHeader("Accept", "application/json").addHeader("Content-Type", "application/json").addparam("xxx", 10).addparam("yyyy", "").addparam("zzz", "dsadsa").httpMethod(HttpMethod.POST).responseType(Policy.class).build();

    //asyncRestTemplate is autowired in the class
    Foo foo = restUtil.callServiceAsync(this.restTemplate);
}
Debopam
  • 3,198
  • 6
  • 41
  • 72
  • Hi. I actually will use it in multi threaded application - not reactive. Will your proposed approach work in this case? – P_M Jan 10 '19 at 20:16
  • This works in Mutithreded environment. Added example createRestUtilForSync().Configure RestTemplate inject it to the Spring component and reuse this – Debopam Jan 10 '19 at 21:28
  • This Rest utiltiy has Sync and Async both. To paralelize multiple Rest call you can use Async version and at the end aggregate. For Single call use Sync, also this can be Paremterized. – Debopam Jan 10 '19 at 21:30
0

You can use technologies provided by Spring Cloud. For example for requesting other web-services the best way is using Feign Client. For the exceptions handling Hystrix.