404 while using Spring cloud FeignClients - spring

This is my setup:
First service(FlightIntegrationApplication) which invoke second service(BaggageServiceApplication) using FeignClients API and Eureka.
Project on github: https://github.com/IdanFridman/BootNetflixExample
First service:
#SpringBootApplication
#EnableCircuitBreaker
#EnableDiscoveryClient
#ComponentScan("com.bootnetflix")
public class FlightIntegrationApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(FlightIntegrationApplication.class).run(args);
}
}
in one of the controllers:
#RequestMapping("/flights/baggage/list/{id}")
public String getBaggageListByFlightId(#PathVariable("id") String id) {
return flightIntegrationService.getBaggageListById(id);
}
FlightIntegrationService:
public String getBaggageListById(String id) {
URI uri = registryService.getServiceUrl("baggage-service", "http://localhost:8081/baggage-service");
String url = uri.toString() + "/baggage/list/" + id;
LOG.info("GetBaggageList from URL: {}", url);
ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);
LOG.info("GetProduct http-status: {}", resultStr.getStatusCode());
LOG.info("GetProduct body: {}", resultStr.getBody());
return resultStr.getBody();
}
RegistryService:
#Named
public class RegistryService {
private static final Logger LOG = LoggerFactory.getLogger(RegistryService.class);
#Autowired
LoadBalancerClient loadBalancer;
public URI getServiceUrl(String serviceId, String fallbackUri) {
URI uri;
try {
ServiceInstance instance = loadBalancer.choose(serviceId);
uri = instance.getUri();
LOG.debug("Resolved serviceId '{}' to URL '{}'.", serviceId, uri);
} catch (RuntimeException e) {
// Eureka not available, use fallback
uri = URI.create(fallbackUri);
LOG.error("Failed to resolve serviceId '{}'. Fallback to URL '{}'.", serviceId, uri);
}
return uri;
}
}
And this is the second service (baggage-service):
BaggageServiceApplication:
#Configuration
#ComponentScan("com.bootnetflix")
#EnableAutoConfiguration
#EnableEurekaClient
#EnableFeignClients
public class BaggageServiceApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(BaggageServiceApplication.class).run(args);
}
}
BaggageService:
#FeignClient("baggage-service")
public interface BaggageService {
#RequestMapping(method = RequestMethod.GET, value = "/baggage/list/{flight_id}")
List<String> getBaggageListByFlightId(#PathVariable("flight_id") String flightId);
}
BaggageServiceImpl:
#Named
public class BaggageServiceImpl implements BaggageService{
....
#Override
public List<String> getBaggageListByFlightId(String flightId) {
return Arrays.asList("2,3,4");
}
}
When invoking the rest controller of flight integration service I get:
2015-07-22 17:25:40.682 INFO 11308 --- [ XNIO-2 task-3] c.b.f.service.FlightIntegrationService : GetBaggageList from URL: http://X230-Ext_IdanF:62007/baggage/list/4
2015-07-22 17:25:43.953 ERROR 11308 --- [ XNIO-2 task-3] io.undertow.request : UT005023: Exception handling request to /flights/baggage/list/4
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 404 Not Found
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
Any idea ?
Thanks,
ray.

Your code looks backwards to me.
The feign client for the baggage service should be declared in the flight service and the baggage service should have a controller that responds on the URL you map in your baggage service client, you should not implement the interface annotated with #FeignClient.
The setup you have now will not have any controller listening on /baggage/list/{flightId} in the baggage service and no Feign client in flight service - the whole point of Feign is to call methods on an interface instead of manually handling URLs, Spring Cloud takes care of auto-instantiating the interface implementation and will use Eureka for discovery.
Try this (or modify so it fits your real world app):
Flight Service:
FlightIntegrationService.java:
#Component
public class FlightIntegrationService {
#Autowired
BaggageService baggageService;
public String getBaggageListById(String id) {
return baggageService.getBaggageListByFlightId(id);
}
}
BaggageService.java:
#FeignClient("baggage-service")
public interface BaggageService {
#RequestMapping(method = RequestMethod.GET, value = "/baggage/list/{flight_id}")
List<String> getBaggageListByFlightId(#PathVariable("flight_id") String flightId);
}
Baggage Service:
BaggageController.java:
#RestController
public class BaggageController {
#RequestMapping("/baggage/list/{flightId}")
public List<String> getBaggageListByFlightId(#PathVariable String flightId) {
return Arrays.asList("2,3,4");
}
}
Remove BaggageService.java and BaggageServiceImpl.java from the Baggage Service

registryService.getServiceUrl("baggage-service", ... replace with
registryService.getServiceUrl("baggage-service")
make sure that matches the right name
remove the localhost part
or only use the http://local part
It only worked for us if you have just the name of the service listed in eureka dashboard, not both

Related

Retry feign client properites

I need to retry feign call for certain http status code and after 3 second for maximum 4 time.
Is there any properties that i can define on my application.yml or i need to write my custom Retryer that implement Retry interface
Feign has a build in Retryer however you can not configure the Retryer via application.yml. I guess the Spring Boot Team assumed that people would use the deprecated Hystrix project for this matter.
Instead of configuring Feign by config you could write a bit of code:
https://cloud.spring.io/spring-cloud-openfeign/reference/html/index.html#creating-feign-clients-manually
In addition you have to map the corresponding status code to RetryableException using a custom ErrorDecoder.
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder errorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
Exception exception = defaultErrorDecoder.decode(s, response);
if(exception instanceof RetryableException){
return exception;
}
if(response.status() == 499){
return new RetryableException("499 blub", response.request().httpMethod(), null );
}
return exception;
}
}
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.errorDecoder(new CustomErrorDecoder())
.target(MyApi.class, "https://api.hostname.com");
}
}
You can use retryable annotation.
Ex: You can throw custom exception when http status code is equal to 404
#Service
public interface MyService {
#Retryable(value = CustomException.class, maxAttempts = 2, backoff = #Backoff(delay = 100))
void retry(String str) throws CustomException;
}

Consume SOAP service using Quarkus

My project requirement is to consume a SOAP service and I am trying to use Quarkus for this purpose. What are the quarkus dependecies hwould I use to acheive this? Is there any sample application I can refer to?
In Spring we can use org.springframework.ws.client.core.support.WebServiceGatewaySupport is there anything similiar in Quarkus.
There is no SOAP client extension at the moment in Quarkus.
There is some discussion to include a CXF extension here : https://github.com/quarkusio/quarkus/issues/4005, you can join the discussion.
A PR is open (not yet finished) for SOAP WS support via CXF but not for SOAP client: https://github.com/quarkusio/quarkus/pull/5538
If you didn't plan to deploy to GraalVM (Quarkus can be deployed both in standard JVM mode and on GraalVM/SubstrateVM as a native application) you can still use any Java library with Quarkus but you will not have any integration with Quarkus itself. So using the CXF Client should works fine in JVM mode : https://cxf.apache.org/docs/how-do-i-develop-a-client.html
we have a new version on https://github.com/quarkiverse/quarkiverse-cxf that you can used for native. It is in beta and can be reference with maven central.
It can be done like #loicmathieu said.
In our realization we have Controller :
#Slf4j
#Path("/xxx")
public class EKWReactiveResource {
#Inject
RequestObject2WsdlRequestObjectConverter converter;
#POST
#Path("/xxxx")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_XML)
public Uni<Response<XsdResponseObject>> wyszukajKsiege(RequestObject request) {
return Uni.createFrom().item(request)
.onItem()
.invoke( req -> log.info(req.toString()))
.map(converter::convert)
.onItem()
.apply(ServiceClient::send);
}
}
ServiceClient :
#Slf4j
public final class ServiceClient {
private final static String ENDPOINT_HTTP = "XXXX";
private final static QName SERVICE_QNAME = new QName("XXXX", "XXXX");
private final static QName SERVICE_QNAME2 = new QName("XXXX", "XXXX");
private static XXXPortType portType;
static {
try {
URL endpointUrl = new URL(ENDPOINT_HTTP);
XXXService service = new XXXService(endpointUrl ,SERVICE_QNAME);
portType = service.getPort(SERVICE_QNAME2, XXXPortType.class);
} catch (MalformedURLException e) {
log.error(e.getMessage(), e);
}
}
public static Response<XsdResponseObject> send(RequestObject requestType) {
return portType.EndpointAsync(requestType);
}
}
And after this we must define ResponseMessageBodyWriter for AsyncResponseImpl> because for some reason it is unknown.
MessageBodyWriter example - you should better write isWriteable method i just dont do this perfectly because this is example only :
#Slf4j
#Provider
#Produces(MediaType.APPLICATION_XML)
public class XXXMessageBodyWriter implements MessageBodyWriter<AsyncResponseImpl<XsdResponseObject>> {
#Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return AsyncResponseImpl.class.isAssignableFrom(aClass);
}
#Override
public void writeTo(AsyncResponseImpl<XsdResponseObject> asyncResponse, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
try {
XsdResponseObject responseObject = asyncResponse.get();
String marshalled = JAXBUtils.marshallToSOAP(responseObject);
log.info(String.format("Response : %s",marshalled));
outputStream.write(marshalled.getBytes());
} catch (InterruptedException | ExecutionException | JAXBException | ParserConfigurationException | SOAPException e) {
log.error(e.getMessage(),e);
}
}
}

Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'request' is not active for the current thread for feign client

I am calling another microservice once my current microservice is up and ready using feign client in my current microservice built using Jhipster.
So my Feign Interface is
package com.persistent.integration.client;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.persistent.integration.service.dto.DataPipelineDTO;
#AuthorizedFeignClient(name = "Integrationconfiguration")
public interface DataPipelinesResourceFeign {
#RequestMapping(value = "/api/data-pipelines", method = RequestMethod.GET)
List<DataPipelineDTO> getAllDataPipelines(#RequestParam(value = "pageable") Pageable pageable );
}
}
And I have implemented ApplicationRunner where I have called feign client method.
#Component
public class ApplicationInitializer implements ApplicationRunner {
#Autowired
private DataPipelinesResourceFeign dataPipelinesResourceFeign;
#Autowired
private ActiveMQListener activeMqListener;
#Override
public void run(ApplicationArguments args) throws Exception {
// TODO Auto-generated method stub
Pageable pageable = PageRequest.of(0, 20);
try {
List <DataPipelineDTO> allStartedDataPipeLines = dataPipelinesResourceFeign.getAllDataPipelines(pageable); //.stream().filter(p->p.getState().equals(State.STARTED)).collect(Collectors.toList());
allStartedDataPipeLines.forEach(datapipe ->
{
try {
activeMqListener.consume(datapipe);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
But after running this, it gives below exception at dataPipelinesResourceFeign.getAllDataPipelines :
com.netflix.hystrix.exception.HystrixRuntimeException: DataPipelinesResourceFeign#getAllDataPipelines(Pageable) failed and no fallback available.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'request' is not active for the current thread; consider
defining a scoped proxy for this bean if you intend to refer to it
from a singleton; nested exception is java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:362)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractB>eanFactory.java:199)
at
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTarge>tSource.java:35)
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.>java:193)
at com.sun.proxy.$Proxy147.getAccessToken(Unknown Source) at
com.persistent.integration.security.oauth2.AuthorizationHeaderUtil.getAuthoriza>tionHeaderFromOAuth2Context(AuthorizationHeaderUtil.java:28)
at
com.persistent.integration.client.TokenRelayRequestInterceptor.apply(TokenRelay>RequestInterceptor.java:23)
at
feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)
at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at
feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
at
rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 68 more Caused by: java.lang.IllegalStateException: No
thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.web.context.request.RequestContextHolder.currentRequestAttr>ibutes(RequestContextHolder.java:131)
at
org.springframework.web.context.request.AbstractRequestAttributesScope.get(Abst>ractRequestAttributesScope.java:42)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:350)
many suggestions on internet were to add listerner RequestContextListener. But problem persisted even if I added listener in webConfigurer.java in onStartup method.
{
servletContext.addListener(RequestContextListener.class);
}
But of no use.
Any leads would be appreciated.
I found a workaround for this. I don't know why TokenRelayRequestIntercepton isn't working but you can use your own RequestInterceptor based on Spring's SecurityContext.
First, define a RequestInterceptor :
public class MyRequestInterceptor implements RequestInterceptor {
public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer";
public MyRequestInterceptor() {
super();
}
#Override
public void apply(RequestTemplate template) {
// demander un token à keycloak et le joindre à la request
Optional<String> header = getAuthorizationHeader();
if (header.isPresent()) {
template.header(AUTHORIZATION, header.get());
}
}
public static Optional<String> getAuthorizationHeader() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getDetails() != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oAuth2AuthenticationDetails =
(OAuth2AuthenticationDetails) authentication.getDetails();
return Optional.of(String.format("%s %s", oAuth2AuthenticationDetails.getTokenType(),
oAuth2AuthenticationDetails.getTokenValue()));
} else {
return Optional.empty();
}
}
}
and then, declare a config class for your feign client using your RequestInterceptor, it should contains something like this :
#Bean(name = "myRequestInterceptor")
public RequestInterceptor getMyRequestInterceptor() throws IOException {
return new MyRequestInterceptor();
}
Your Feign client shoud look like this:
#FeignClient(name = "SERVICE_NAME", configuration = MyFeignConfiguration.class)
public interface MyRestClient {
I had the same issue with Feign Client running on startup using ApplicationRunner and I came up with following solution.
I defined my FeignClientsConfiguration with OAuth2FeignRequestInterceptor, which accepts predefined bean DefaultOAuth2ClientContext and OAuth2 configuration OAuth2ProtectedResourceDetails:
#Configuration
public class MyConfig extends FeignClientsConfiguration {
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor( DefaultOAuth2ClientContext oAuth2ClientContext, MyOauth2Properties properties) {
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resourceDetails(properties));
}
#Bean
public DefaultOAuth2ClientContext oAuth2ClientContext() {
return new DefaultOAuth2ClientContext();
}
private OAuth2ProtectedResourceDetails resourceDetails(MyOauth2Properties oauth2Properties) {
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setAccessTokenUri(oauth2Properties.getAccessTokenUri());
resourceDetails.setUsername(oauth2Properties.getUsername());
resourceDetails.setPassword(oauth2Properties.getPassword());
resourceDetails.setClientId(oauth2Properties.getClientId());
return resourceDetails;
}
}
Your feign client will look something like this:
#FeignClient(url = "http://localhost:8080/api/v1")
public interface FeignClient {
}
After all this, calling FeignClient from ApplicationRunner.run() works fine.
Spring Boot 2.2.6

Vertx instance variable is null in Spring context

I defined a Spring Boot App as a Verticle as follows:
#SpringBootApplication
public class SpringAppVerticle extends AbstractVerticle {
private Vertx myVertx;
#Override
public void start() {
SpringApplication.run(SpringAppVerticle.class);
System.out.println("SpringAppVerticle started!");
this.myVertx = vertx;
}
#RestController
#RequestMapping(value = "/api/hello")
public class RequestController {
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
public void getEcho() {
JsonObject message = new JsonObject()
.put("text", "Hello world!");
myVertx.eventBus().send(EchoServiceVerticle.ADDRESS, message, reply -> {
JsonObject replyBody = (JsonObject) reply.result().body();
System.out.println(replyBody.encodePrettily());
});
}
}
}
I have a second non-Spring Verticle that is basically a echo service:
public class EchoServiceVerticle extends AbstractVerticle {
public static final String ADDRESS = "echo-service";
#Override
public void start() {
System.out.println("EchoServiceVerticle started!");
vertx.eventBus().consumer(EchoServiceVerticle.ADDRESS, message -> {
System.out.println("message received");
JsonObject messageBody = (JsonObject) message.body();
messageBody.put("passedThrough", "echo-service");
message.reply(messageBody);
});
}
}
The problem is that I get a nullpointer at line myVertx.eventbus().send in SpringAppVerticle class as the myVertx variable is null.
How do I properly instantiate a Vertx variable in a Spring context in order that I can exchange message between my both verticles?
My project can be found here: https://github.com/r-winkler/vertx-spring
The reason of the exception is the following:
SpringAppVerticle bean that is created during spring init is another object than starts the spring boot application. So you have two objects, one that has start() method invoked and another one that doesn't. Second one actually handles requests. So what you need is to register verticles as spring beans.
For samples of vertx/spring interoperability please refer to vertx examples repo.
P.S. I've created a pull request to your repo to make your example work.

Test spring based rest service with JerseyTest and Grizzly: dependency injection not working

Here my Rest WS:
#Path("/personService")
#Service
public class PersonRestService {
Logger logger = LoggerFactory.getLogger(PersonRestService.class);
#Autowired
private PersonService personService;
#GET
#Path("{id}")
#Produces({ MediaType.APPLICATION_JSON })
public Person getPersonByID(#PathParam("id") String id) {
logger.debug("getItemByID with id {}", id);
return personService.getPersonById(id);
}
Here is the unit test:
public class PersonServiceRestTest extends JerseyTest {
public PersonServiceRestTest() throws Exception {
super(new WebAppDescriptor.Builder("com.intesasanpaolo.web.rest.service").
contextPath("test")
.contextParam("contextConfigLocation", "classpath*:application-context/web-test-context.xml")
.contextListenerClass(ContextLoaderListener.class)
.build());
}
#Test
public void testGetPerson() {
Client client = Client.create();
WebResource webResource = client.resource("http://localhost:9998/test/personService/1");
ClientResponse response = webResource.type(MediaType.APPLICATION_JSON).get(ClientResponse.class);
System.out.println(response);
}
}
When I run the test I receive and exception on personService.getPersonById(id) since personService is not autowired.
It seems that all the rest services created by grizzly do not share the spring context defined above:
INFO: Scanning for root resource and provider classes in the packages:
com.intesasanpaolo.web.rest.service
10-feb-2014 19.10.01 com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
class com.intesasanpaolo.web.rest.service.PersonRestService
class com.intesasanpaolo.web.rest.service.MyResource
I read a lot of discussion but still no solution.
Any idea?
Kind regards.
Massimo
Ok, I found the problem.
The context was not shared because the WebAppDescriptor was not configured with the SpringServlet.
The working configuration is the following.
super(new WebAppDescriptor.Builder("com.intesasanpaolo.web.rest.service")
.contextPath("test")
.contextParam("contextConfigLocation", "classpath*:application-context/web-test-context.xml")
.servletClass(SpringServlet.class)
.initParam("com.sun.jersey.api.json.POJOMappingFeature", "true")
.contextListenerClass(ContextLoaderListener.class)
.build());
Hope this could help someone ;)
Max

Resources