56

@FeignClient(name = "test", url="http://xxxx")

How can I change the feign URL (url="http://xxxx") during the runtime? because the URL can only be determined at run time.

Soumitri Pattnaik
  • 3,246
  • 4
  • 24
  • 42
liucyu
  • 571
  • 1
  • 4
  • 3

10 Answers10

67

You can add an unannotated URI parameter (that can potentially be determined at runtime) and that will be the base path that will be used for the request. E.g.:

    @FeignClient(name = "dummy-name", url = "https://this-is-a-placeholder.com")
    public interface MyClient {
        @PostMapping(path = "/create")
        UserDto createUser(URI baseUrl, @RequestBody UserDto userDto);
    }

And then the usage will be:

    @Autowired 
    private MyClient myClient;
    ...
    URI determinedBasePathUri = URI.create("https://my-determined-host.com");
    myClient.createUser(determinedBasePathUri, userDto);

This will send a POST request to https://my-determined-host.com/create (source).

Jasper Citi
  • 1,673
  • 1
  • 23
  • 31
asherbret
  • 5,439
  • 4
  • 38
  • 58
30

Feign has a way to provide the dynamic URLs and endpoints at runtime.

The following steps have to be followed:

  1. In the FeignClient interface we have to remove the URL parameter. We have to use @RequestLine annotation to mention the REST method (GET, PUT, POST, etc.):

    @FeignClient(name="customerProfileAdapter")
    public interface CustomerProfileAdaptor {

        // @RequestMapping(method=RequestMethod.GET, value="/get_all")
        @RequestLine("GET")
        public List<Customer> getAllCustomers(URI baseUri); 
     
        // @RequestMapping(method=RequestMethod.POST, value="/add")
        @RequestLine("POST")
        public ResponseEntity<CustomerProfileResponse> addCustomer(URI baseUri, Customer customer);
     
        @RequestLine("DELETE")
        public ResponseEntity<CustomerProfileResponse> deleteCustomer(URI baseUri, String mobile);
    }
    
  1. In RestController you have to import FeignClientConfiguration
  2. You have to write one RestController constructor with encoder and decoder as parameters.
  3. You need to build the FeignClient with the encoder, decoder.
  4. While calling the FeignClient methods, provide the URI (BaserUrl + endpoint) along with rest call parameters if any.

    @RestController
    @Import(FeignClientsConfiguration.class)
    public class FeignDemoController {
    
        CustomerProfileAdaptor customerProfileAdaptor;
    
        @Autowired
        public FeignDemoController(Decoder decoder, Encoder encoder) {
            customerProfileAdaptor = Feign.builder().encoder(encoder).decoder(decoder) 
               .target(Target.EmptyTarget.create(CustomerProfileAdaptor.class));
        }
    
        @RequestMapping(value = "/get_all", method = RequestMethod.GET)
        public List<Customer> getAllCustomers() throws URISyntaxException {
            return customerProfileAdaptor
                .getAllCustomers(new URI("http://localhost:8090/customer-profile/get_all"));
        }
    
        @RequestMapping(value = "/add", method = RequestMethod.POST)
        public ResponseEntity<CustomerProfileResponse> addCustomer(@RequestBody Customer customer) 
                throws URISyntaxException {
            return customerProfileAdaptor
                .addCustomer(new URI("http://localhost:8090/customer-profile/add"), customer);
        }
    
        @RequestMapping(value = "/delete", method = RequestMethod.POST)
        public ResponseEntity<CustomerProfileResponse> deleteCustomer(@RequestBody String mobile)
                throws URISyntaxException {
            return customerProfileAdaptor
                .deleteCustomer(new URI("http://localhost:8090/customer-profile/delete"), mobile);
        }
    }
ikhvjs
  • 5,316
  • 2
  • 13
  • 36
Sandip Nirmal
  • 415
  • 4
  • 7
  • Thanks. But can I read the url from properties/yaml file, injecting with `@Value` or `@ConfigurationProperties`? I cannot find how to do it. – WesternGun Sep 16 '19 at 10:38
  • 3
    This approaches changes the signature of the methods and is therefor not a good solution. See the answer of @Forest10 below for a better solution using Spring. – Simon Zambrovski Oct 01 '19 at 12:55
10

I don`t know if you use spring depend on multiple profile. for example: like(dev,beta,prod and so on)

if your depend on different yml or properties. you can define FeignClientlike:(@FeignClient(url = "${feign.client.url.TestUrl}", configuration = FeignConf.class))

then

define

feign:
  client:
    url:
      TestUrl: http://dev:dev

in your application-dev.yml

define

feign:
  client:
    url:
      TestUrl: http://beta:beta

in your application-beta.yml (I prefer yml).

......

thanks god.enjoy.

Forest10
  • 279
  • 3
  • 11
9

use feign.Target.EmptyTarget

@Bean
public BotRemoteClient botRemoteClient(){
    return Feign.builder().target(Target.EmptyTarget.create(BotRemoteClient.class));
}

public interface BotRemoteClient {

    @RequestLine("POST /message")
    @Headers("Content-Type: application/json")
    BotMessageRs sendMessage(URI url, BotMessageRq message);
}

botRemoteClient.sendMessage(new URI("http://google.com"), rq)
sergio5990
  • 91
  • 1
  • 1
8

You can create the client manually:

@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

    @Autowired
    public FooController(ResponseEntityDecoder decoder, SpringEncoder encoder, Client client) {
        this.fooClient = Feign.builder().client(client)
            .encoder(encoder)
            .decoder(decoder)
            .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
            .target(FooClient.class, "http://PROD-SVC");
        this.adminClient = Feign.builder().client(client)
            .encoder(encoder)
            .decoder(decoder)
            .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
            .target(FooClient.class, "http://PROD-SVC");
     }
}

From documentation: https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#_creating_feign_clients_manually

Jeff
  • 1,871
  • 1
  • 17
  • 28
4

In interface you can change url by Spring annotations. The base URI is configured in yml Spring configuration.

   @FeignClient(
            name = "some.client",
            url = "${some.serviceUrl:}",
            configuration = FeignClientConfiguration.class
    )

public interface SomeClient {

    @GetMapping("/metadata/search")
    String search(@RequestBody SearchCriteria criteria);

    @GetMapping("/files/{id}")
    StreamingResponseBody downloadFileById(@PathVariable("id") UUID id);

}
1

A simple way is to use Interceptor: RequestInterceptor

feign will replace the target url if you set target host in interceptor:

  // source code of feign
  Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(template);
  }

  public Request apply(RequestTemplate input) {
    if (input.url().indexOf("http") != 0) {
      input.target(url());
    }
    return input.request();
  }

Custom you interceptor:

public class DynamicFeignUrlInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        if (isNotDynamicPath(template)) {
            return;
        }
        template.target(getHost());
    }

    private boolean isNotDynamicPath(RequestTemplate template) {
        // TODO Determine whether it is dynamic according to your logic
        return false;
    }

    private String getHost() {
        // use any host you want, host must be contained key word of 'http' 
        return "http://example.com";
    }

}

The advantage of this is that if there is already a large amount of feign client code, it can be implemented without modifying the code.

Tony
  • 29
  • 2
  • 9
0

Use @PathVariable like this:

@Service    
@FeignClient(name = "otherservicename", decode404 = true)    
public interface myService {

@RequestMapping(method = RequestMethod.POST, value = "/basepath/{request-path}")
ResponseEntity<String> getResult(@RequestHeader("Authorization") String token,
                                                      @RequestBody HashMap<String, String> reqBody,
                                                      @PathVariable(value = "request-path") String requestPath);
}

Then from service, construct the dynamic url path and send the request:

String requestPath = "approve-req";

ResponseEntity<String> responseEntity = myService.getResult(
                token, reqBody, requestPath);

Your request url will be at: "/basepath/approve-req"

inhaler
  • 175
  • 1
  • 2
  • 12
-2

I prefer to build feign client by configuration to pass a url at run time (in my case i get the url by service name from consul discovery service)

so i extend feign target class as below:

public class DynamicTarget<T> implements Target<T> {
private final CustomLoadBalancer loadBalancer;
private final String serviceId;
private final Class<T> type;

public DynamicTarget(String serviceId, Class<T> type, CustomLoadBalancer loadBalancer) {
    this.loadBalancer = loadBalancer;
    this.serviceId = serviceId;
    this.type = type;
}

@Override
public Class<T> type() {
    return type;
}

@Override
public String name() {
    return serviceId;
}

@Override
public String url() {
    return loadBalancer.getServiceUrl(name());
}

@Override
public Request apply(RequestTemplate requestTemplate) {
    requestTemplate.target(url());
    return requestTemplate.request();
}
}


 var target = new DynamicTarget<>(Services.service_id, ExamsAdapter.class, loadBalancer);
Mohamed.Abdo
  • 2,054
  • 1
  • 19
  • 12
-3
                package commxx;

                import java.net.URI;
                import java.net.URISyntaxException;

                import feign.Client;
                import feign.Feign;
                import feign.RequestLine;
                import feign.Retryer;
                import feign.Target;
                import feign.codec.Encoder;
                import feign.codec.Encoder.Default;
                import feign.codec.StringDecoder;

                public class FeignTest {

                    public interface someItfs {

                 
                        @RequestLine("GET")
                        String getx(URI baseUri);
                    }

                    public static void main(String[] args) throws URISyntaxException {
                        String url = "http://www.baidu.com/s?wd=ddd";  //ok..
                        someItfs someItfs1 = Feign.builder()
                                // .logger(new FeignInfoLogger()) // 自定义日志类,继承 feign.Logger
                                // .logLevel(Logger.Level.BASIC)// 日志级别
                                // Default(long period, long maxPeriod, int maxAttempts)
                                .client(new Client.Default(null, null))// 默认 http
                                .retryer(new Retryer.Default(5000, 5000, 1))// 5s超时,仅1次重试
                            //  .encoder(Encoder)
                            //  .decoder(new StringDecoder())
                                .target(Target.EmptyTarget.create(someItfs.class));

                //       String url = "http://localhost:9104/";
                //         
                        System.out.println(someItfs1.getx(new URI(url)));
                    }

                }
  • Could you put some context around your answer. Also please clean-up the code, removing references to Baidu and non-relevant commented out code. – Marcel Dumont Nov 20 '20 at 18:37