I am trying to use Spring boot to communicate with a backend server which does not support encoded URL. I tried intercepting the RestTemplate and modifying the query parameter but it does not seems to work. What should be the proper way to doing it?
The code for feign client is
#FeignClient(url = "${gateway.api}",
configuration = BackendConfig.class)
#RequestMapping("/v1/")
public interface GatewayClient {
#GetMapping(path = "/authorize")
String getAuthorization(#RequestParam(name = "cburl") String url);
}
Now if I invoke GatewayClient.authorize("http://example.com") I can see that it gets called ${gateway.api}/v1/authorize?cburl=http:%2F%2Fexample.com which is not recognized by the backend service. However, $(gateway.api}/v1/authorize?cburl=http://example.com works.
The BackendConfig class is given below for reference
class BackendConfig {
#Autowired
ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
public Decoder springDecoder() { return new ResponseEntityDecover(new SpringDecoder(messageConverters); }
#Bean
public MyInterceptor requestInterceptor() {
return new MyInterceptor();
}
public class MyInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
String lines;
try {
lines = URLDecoder.decode(String.valueOf(template.queries().get("url")), "UTF-8");
template.queries.put("url", Collections.singletonList(lines));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
But I am getting UnsupportedOperationException and I believe at this point I can not modify the queries. Any suggestion is highly appreciated. (You will notice that the query parameter is leaving ':' (colon) as it is instead of encoding it to %3A).
Related
I am working on webclient for various HTTP methods (GET,PATCH,POST,DELETE). These are created separately and invoke separately. I am looking to make it as a generic webclient component so minimum changes needs to do in the future. Below is the code for GET and POST. PATCH and Delete is also on similar lines. Please let me know how can I proceed to make generic webclient for various HTTP methods.
#Component
public class HuntsCreateNewCollectionAdapter {
#Autowired
AppProperties properties;
#Autowired
WebclientCollectionConfig webclientCollectionConfig;
public Mono<ResponseEntity<HuntsCollectionDto>> createCollectionAPI(RequestContext context, String title) {
LOGGER.debug("Create Collection API call");
CollectionInputDto collectionInput = CollectionInputDto.builder().title(title).build();
String json = AppJsonUtil.getJsonAsString(collectionInput);
Mono<ResponseEntity<HuntsCollectionDto>> result = webclientCollectionConfig.collectionBuilder().post()
.uri(properties.getCollectionUrl())
.headers(header -> header.addAll(webclientCollectionConfig.getHeaders(context)))
.body(BodyInserters.fromValue(json)).exchangeToMono(response -> {
return response.toEntity(HuntsCollectionDto.class);
});
return result;
}
}
#Component
public class HuntsGetCollectionAdapter {
#Autowired
AppProperties properties;
#Autowired
WebclientCollectionConfig webclientCollectionConfig;
public Mono<ResponseEntity<HuntsCollectionDto>> getCollectionAPI(RequestContext context, String collectionId) {
LOGGER.debug("Get Collection API call");
return webclientCollectionConfig.collectionBuilder().get()
.uri(properties.getCollectionUrl() + "/" + collectionId)
.headers(header -> header.addAll(webclientCollectionConfig.getHeaders(context)))
.exchangeToMono(response -> {
return response.toEntity(HuntsCollectionDto.class);
});
}
}
#Component
public class WebclientCollectionConfig {
#Autowired
AppProperties appProperties;
#Bean
public WebClient collectionBuilder() {
return WebClient.builder().baseUrl(appProperties.getCollectionbaseUrl())
.defaultHeader("Content-Type", "application/json").build();
}
}
I was facing an issue that my GET requests were being changed to POST due the RequestHeader and PathVariable that were being interpreted as body of the request in Feign Client.
Interceptor
public class OpenFeignConfiguration implements RequestInterceptor {
#Value("${key:}")
private String key;
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Override
public void apply(RequestTemplate template) {
template.header("key", key);
}
}
And the Feign Client
#FeignClient(name = "feignClient", url = "${client.url}", configuration = OpenFeignConfiguration.class)
public interface FeignClient {
#GetMapping(value = "/path/?test=({var1} and {var2})")
public Object test(String body, #PathVariable("var1") String var1, #PathVariable("var2") String var2);
}
The solution that I found is that you have to change Springs Feign contract to be Feign one so:
public class OpenFeignConfiguration implements RequestInterceptor {
#Value("${key:}")
private String key;
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Bean
public Contract feignContract() {
return new Contract.Default();
}
#Override
public void apply(RequestTemplate template) {
template.header("key", key);
}
}
And the client now must use the Feign annotation:
#FeignClient(name = "feignClient", url = "${client.url}", configuration = OpenFeignConfiguration.class)
public interface FeignClient {
#RequestLine("GET /path/?test=({var1} and {var2})")
public Object test(#Param("var1") String originator, #Param("var2") String receiver);
}
Hope that helps anyone having same issue that I had.
I have a working example of TCPSocketClient using Spring Integration with hard coded host name and the port.
How to modify this example to accept the localhost and the port 5877 to be passed dynamically?
i.e. Is it possible to call the exchange method like ExchangeService.exchange(hostname, port, request) instead of ExchangeService.exchange(request)
If so how does those parameter be applied on to the client bean?
#Configuration
public class AppConfig {
#Bean
public IntegrationFlow client() {
return IntegrationFlows.from(ApiGateway.class).handle(
Tcp.outboundGateway(
Tcp.netClient("localhost", 5877)
.serializer(codec())
.deserializer(codec())
).remoteTimeout(10000)
)
.transform(Transformers.objectToString())
.get();
}
#Bean
public ByteArrayCrLfSerializer codec() {
ByteArrayCrLfSerializer crLfSerializer = new ByteArrayCrLfSerializer();
crLfSerializer.setMaxMessageSize(204800000);
return crLfSerializer;
}
#Bean
#DependsOn("client")
public ExchangeService exchangeService(ApiGateway apiGateway) {
return new ExchangeServiceImpl(apiGateway);
}
}
public interface ApiGateway {
String exchange(String out);
}
public interface ExchangeService {
public String exchange(String request);
}
#Service
public class ExchangeServiceImpl implements ExchangeService {
private ApiGateway apiGateway;
#Autowired
public ExchangeServiceImpl(ApiGateway apiGateway) {
this.apiGateway=apiGateway;
}
#Override
public String exchange(String request) {
String response = null;
try {
response = apiGateway.exchange(request);
} catch (Exception e) {
throw e;
}
return response;
}
}
For dynamic processing you may consider to use a Dynamic Flows feature from Spring Integration Java DSL: https://docs.spring.io/spring-integration/docs/current/reference/html/#java-dsl-runtime-flows
So, whenever you receive a request with those parameters, you create an IntegrationFlow on the fly and register it with the IntegrationFlowContext. Frankly, we have exactly sample in the docs for your TCP use-case:
IntegrationFlow flow = f -> f
.handle(Tcp.outboundGateway(Tcp.netClient("localhost", this.server1.getPort())
.serializer(TcpCodecs.crlf())
.deserializer(TcpCodecs.lengthHeader1())
.id("client1"))
.remoteTimeout(m -> 5000))
.transform(Transformers.objectToString());
IntegrationFlowRegistration theFlow = this.flowContext.registration(flow).register();
Spring #Autowire field is null even though it works fine in other classes successfully.
public class SendRunner implements Runnable {
private String senderAddress;
#Autowired
private SubscriberService subscriberService;
public SendRunner(String senderAddress) {
this.senderAddress = senderAddress;
}
#Override
public void run() {
sendRequest();
}
private void sendRequest() {
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("subscriberId", senderAddress);
HttpEntity<?> entity = new HttpEntity<Object>(dataMap, httpHeaders);
Subscriber subscriber = subscriberService.getSubscriberByMsisdn(senderAddress);
} catch (Exception e) {
logger.error("Error occurred while trying to send api request", e);
}
}
Also this class is managed as a bean in the dispatcher servlet :
<bean id="SendRunner" class="sms.dating.messenger.connector.SendRunner">
</bean>
In here i'm getting a null pointer exception for subscriberService. What would be the possible reason for this? Thanks in advance.
Can you please try with below code snippet
#Configuration
public class Someclass{
#Autowired
private SubscriberService subscriberService;
Thread subscriberThread = new Thread() {
#Override
public void run() {
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("subscriberId", senderAddress);
HttpEntity<?> entity = new HttpEntity<Object>(dataMap, httpHeaders);
Subscriber subscriber = subscriberService.getSubscriberByMsisdn(senderAddress);
} catch (Exception e) {
logger.error("Error occurred while trying to send api request", e);
}
}
};
}
Can you please annotate your SendRunner class with #Component or #Service and include the SendRunner package in componentscanpackage
Your bean not in Spring Managed context, below can be the reasons.
Package sms.dating.messenger.connector not in Component scan.
You are moving out of the Spring context by creating an object with new (see below),
this way you will not get the autowired fields.
SendRunner sendRunner = new SendRunner () ,
sendRunner.sendRequest();
Just check how I implement. Hope this will help.
#RestController
public class RestRequest {
#Autowired
SendRunner sendRunner;
#RequestMapping("/api")
public void Uri() {
sendRunner.start();
}
}
SendRunner class
#Service
public class SendRunner extends Thread{
#Autowired
private SubscriberService subscriberService;
#Override
public void run() {
SendRequest();
}
private void SendRequest() {
System.out.println("Object is " + subscriberService);
String senderAddress = "address";
subscriberService.getSubscriberByMsisdn(senderAddress);
}
}
Below are the logs printed when I hit the REST api.
Object is com.example.demo.SubscriberService#40f33492
I use spring cloud & feign client with my app.And I want to set the param 'accept-language' to headers when call feign clients.
I found the similar questions at [Using #Headers with dynamic values in Feign client + Spring Cloud (Brixton RC2)
Ask]1,but I don't know how to set header param.Here is my code:
I set MyDefaultFeignConfig at app.java
#EnableFeignClients(basePackages = {defaultConfiguration = MyDefaultFeignConfig.class)
And MyDefaultFeignConfig.java :
#Configuration
public class MyDefaultFeignConfig {
private String requestLanguage = "zh";
#Bean
RequestInterceptor feignRequestInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("accept-language", requestLanguage);
}
};
}
//doesn't work
public static void updateBean(String requestLanguage) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyDefaultFeignConfig.class);
try {
System.out.println(applicationContext.getBean("feignRequestInterceptor"));
} catch (NoSuchBeanDefinitionException e) {
System.out.println("Bean not found");
}
BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
beanFactory.registerBeanDefinition("feignRequestInterceptor",
BeanDefinitionBuilder.genericBeanDefinition(String.class)
.addConstructorArgValue(new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("accept-language", requestLanguage);
}
})
.getBeanDefinition()
);
}
}
My Gateway controller is :
#Autowired
private LeaseOrderRemoteService leaseOrderRemoteService;
#RequestMapping(value = "/Discovery/order/unifiyInit", method = RequestMethod.GET)
public Message unifiyOrderInit(#RequestHeader("accept-language") String language) {
MyDefaultFeignConfig.updateBean(language);
return leaseOrderRemoteService.unifiyOrderInit();
}
My feign clients Controller is:
public Message unifiyOrderInit(#RequestHeader("accept-language") String language) {
//...
}
And I can only get the value of "accept-language" as MyDefaultFeignConfig config the first time set #Bean.How can I set the value of "accept-language" from Gateway to feign client.Please help me,thinks! Any suggestions are grateful and best regards!