I'm using Spring cloud starter gateway 2.0.1.RELEASE along with Starter netflix hystrix. Is it possible to provide a HystrixCommand in route definition like below?.
builder.routes()
.route( r -> r.path("path")
.and().method(HttpMethod.GET)
.filters(f -> f.hystrix("myHystrixCommandName"))
.uri("/destination")
.id("route_1"));
My goal is to execute a fallback method without forwarding the request to a fallback uri.
Also I cannot use static fallback uri option as I need path params and request params to determine the fallback response. Any help is highly appreciated!.
I faced the same issue. This is how I solved it:
First, These are the routes:
builder.routes()
.route( r -> r.path("/api/{appid}/users/{userid}")
.and().method(HttpMethod.GET)
.filters(f -> f.hystrix("myHystrixCommandName")
.setFallbackUri("forward:/hystrixfallback"))
.uri("/destination")
.id("route_1"));
The path predicate processes the url and extracts the path variables, those are saved into ServerWebExchange.getAttributes(), with a key defined as PathRoutePredicate.URL_PREDICATE_VARS_ATTR or ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE
You can find more information about path predicate here
Second, I created a #RestController to handle the forward from Hystrix injecting the ServerWebExchange:
#RestController
#RequestMapping("/hystrixfallback")
public class ServiceFallBack {
#RequestMapping(method = {RequestMethod.GET})
public String get(ServerWebExchange serverWebExchange) {
//Get the PathMatchInfo
PathPattern.PathMatchInfo pathMatchInfo = serverWebExchange.getAttribute(ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
//Get the template variables
Map<String, String> urlTemplateVariables = pathMatchInfo.getUriVariables();
//TODO Logic using path variables
return "result";
}
}
Related
I have a question on JAXB mapping using org.springframework.ws.server.endpoint.annotations.
I was able to generate Java domain object with provided *.xsd. The thing is after I define my endpoint with
#PayloadRoot, I have to wrap my request and response as below to successfully trigger the method and return a result:
#PayloadRoot( localPart = "PmtAuthAddRequest",
namespace = "http://*com/emb/webseries")
#ResponsePayload
public JAXBElement billPayment(#RequestPayload JAXBElement var1){
PmtAuthAddResponseType response=billPaymentHandler.execute(var1.getValue());
return of.createPmtAuthAddResponse(response); // Used ObjectFactory to create JAXBElement.
}`
`
From all the tutorial I see, they dont need to wrap it as JAXBElement to return the correct type, but the below code does not work for me:
`
`#PayloadRoot( localPart = "PmtAuthAddRequest",
namespace = "http://*com/emb/webseries")
#ResponsePayload
public PmtAuthAddResponseType billPayment(#RequestPayload PmtAuthAddRequestType> var1){
PmtAuthAddResponseType response=billPaymentHandler.execute(var1.getValue());
return response;
}`
`
Do you guys know why? How can I resolve this? Thanks
I tried without wrapping it as JAXBElement, but soap UI return with error message:
`no adapter for endpoint [public com.*.*.*.webseries.billpay.CustPayee50InqResponseType com.*.Endpoint.InquirePayeeEndpoint.inquirepayees(com.*.*.*.webseries.billpay.CustPayee50InqRequestType) throws javax.xml.bind.JAXBException]: Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?</faultstring>
`
Actually solved my own question....
The way to do it is to add #XmlRootElement under generated Java class from JAXB2 with below to correctly mapping:
#XmlRootElement(namespace = "http://..*/emb/webseries",name = "CustPayee50InqRequest")
The name should match with the localPart provided name from #PayloadRoot.
Added both for request and response makes it work for me
I was instructed to create webservices ( with Spring-Boot ). My colleague gave me the url of the webservice and it looks like this : http://172.20.40.4:8080/Oxalys_WS/stock/ITM=1559
In general we create a RestController with the url :
#RestController
#RequestMapping("stock")
public class StockController {
#Autowired
private StockService stockService;
#GetMapping(value = "/{code}", produces = "application/json")
public JsonModel getByCode(#PathVariable String code) {
JsonModel jsonModel = new JsonModel();
final Map<String, Object> data = new HashMap<>();
List<Stock> stock = stockService.getByCode(code);
data.put("stock", stock);
data.put("stockTotal", stockService.getTotal(code));
jsonModel.setDatas(data);
return jsonModel;
}
}
So is it normal to create a Restful Spring-Boot webservice with a parameter in the url ?
Spring provides parameter in two standard way.
Query Param : http://172.20.40.4:8080/Oxalys_WS/stock?ITM=1559
Path Variable : http://172.20.40.4:8080/Oxalys_WS/stock/1559
Query Param :- It is a typical old way to pass some value as QueryParam with using of some variable starts with ?(Question Mark) and value is assigned using =(equals).
PathVariable :- this is a newer pattern introduce for REST-api Services. URL must be structured such in a way that this should not look too messy if multiple parameters need to pass within a URL.
For more info Navigate this link
Yes, you can have the one in your URL
When you are required to have the path variable, you can give in the Request URL
I'm implementing a client feign with a hystrix fallback; My problem is that the fallback class calls an API that uses some different data from the clientFeign.
So, my question is: is there a way to pass some additional parameters to my feign, so it can be used just by the fallback class?
#FeignClient(name = "${feign.inventory.name}", url = "${feign.inventory.url:}", fallbackFactory = StockFallback.class)
public interface StockClient {
#RequestMapping(method = GET, value = "/{sku}/{groupId}", consumes = APPLICATION_JSON_VALUE,
produces = APPLICATION_JSON_VALUE)
List<ItemStock> getStockSkuAndInventoryGroup(#PathVariable("sku") final String sku,
#PathVariable("groupId") final String groupId);
}
As spencergibb pointted there is no possibilities of passing an adittional parameter to use in a histrix feign fall back.
So we developed anew end-point which uses the same data as the original, so that the fallback take on clearly and transparent.
When i send a POST request using netflix client , the json properties are blank when it hits the service consumer.
Below is my interface
#FeignClient(name = "NLPService", configuration = FooConfiguration.class )
public interface NLPServiceConsumer extends TempInterface {
}
public interface TempInterface {
#RequestMapping("/greeting")
String greeting();
#RequestMapping(method = RequestMethod.POST,value="/nlp",
consumes="application/json",produces="application/json")
NLPResponse identifyTags(NLPInputToBeTransformed nlpInputToBeTransformed);
#RequestMapping(method = RequestMethod.GET,value="/nlpGetMethod",
produces="application/json")
NLPResponse identifyTagsTest();
}
Method identifyTagsTest works and I am able to successfully get the response .
This method is a GET method with no input
When I try a POST method , passing a object as parameter , at the end point service implementation , the object attributes are null .
Has anybody faced such issue ? Is there any mistake in my configuration ?
The problem was not at the feign client. It was at the service implementation
Spent almost a day on this issue .
The RestController also has to specify #RequestBody ( apart from the shared interface )
can #FeignClient extend - and #RestController implement - a common, fully-annotated Interface?
I have a number of clients for which a "global" RequestInterceptor has been defined. For one of the clients I need this "global" interceptor to be excluded. Is it possible to override the full set of RequestInterceptors for a particular FeignClient?
#FeignClient(value = "foo", configuration = FooClientConfig.class)
public interface FooClient {
//operations
}
#Configuration
public class FooClientConfig{
//How do I exclude global interceptors from this client configuration?
}
The spring-cloud-netflix version in use is 1.1.0 M5
It seems there is no easy way to override the global interceptor.
I think you could do it like this:
#Configuration
public class FooClientConfig{
#Bean
RequestInterceptor globalRequestInterceptor() {
return template -> {
if (template.url().equals("/your_specific_url")) {
//don't add global header for the specific url
return;
}
//add header for the rest of requests
template.header(AUTHORIZATION, String.format("Bearer %s", token));
};
}
}
Based on the issue stated here. Instead of excluding interceptors, you need to define different feign clients for each API. Add your interceptors based on your needs.
public class ConfigOne {
#Bean
public InterceptorOne interceptorOne(AdditionalDependency ad) {
return new InterceptorOne(ad);
}
}
Just make sure you don't use #Configuration annotation on above class.
Instead, importing this bean on client definition would be a working solution.
#FeignClient(name = "clientOne", configuration = ConfigOne.class)
public interface ClientOne { ... }
An enhanced way of solving this is to pass a custom header to your request like:
#PostMapping("post-path")
ResponseEntity<Void> postRequest(#RequestHeader(HEADER_CLIENT_NAME) String feignClientName, #RequestBody RequestBody requestBody);
I want to set the header in interceptor for only this feign client. Before setting the header, first, the interceptor checks HEADER_CLIENT_NAME header if exists and have the desired value:
private boolean criteriaMatches(RequestTemplate requestTemplate) {
Map<String, Collection<String>> headers = requestTemplate.headers();
return headers.containsKey(HEADER_CLIENT_NAME)
&& headers.get(HEADER_CLIENT_NAME).contains("feign-client-name");
}
Thus, you can check before setting the basic authentication. In interceptor:
#Override
public void apply(RequestTemplate template) {
if (criteriaMatches(template)) {
/*apply auth header*/
}
}
In this way, other feign client's requests won't be manipulated by this interceptor.
Finally, I set the feignClientName to the request:
feignClient.postRequest("feign-client-name", postBody);
One way to do this to remove the #Configuration annotation from the FooClientConfig class as in the current situation it is applied globally.
And then use
#FeignClient(value = "foo", configuration = FooClientConfig.class)
on all of the feign clients you want to use the config with.