How to pass multiple RequestHeader when using #FeignClient - spring-boot

I need to pass multiple Request Headers using #FeignClient
When its one header of type String the #RequestHeader works fine but with multiple I get RequestHeader.value() was empty on parameter 0, while starting the spring boot error .
#RequestMapping(value="/*********employees", method= RequestMethod.GET , consumes = MediaType.APPLICATION_JSON_VALUE)
EmployeeData fetchWorkdayEmployeess(#RequestHeader Map<String, Object> headers);
as well as I tried using #HeaderMap
#RequestMapping(value="/*********employees", method= RequestMethod.GET , consumes = MediaType.APPLICATION_JSON_VALUE)
EmployeeData fetchWorkdayEmployeess(#HeaderMap Map<String, Object> headers);
I also tried passing multiple #RequestHeaders as parameters but it doesn't seem to work

I needed to use a custom RequestInterceptor
#Configuration
class FeignCustomHeaderConfig {
#Bean
public CSODHeaderAuthRequestInterceptor basicAuthRequestInterceptor() {
try {
return new HeaderAuthRequestInterceptor(token_map);
} catch (Exception e) {
log.error(e.getLocalizedMessage());
}
return new CSODHeaderAuthRequestInterceptor(null);
}
class HeaderAuthRequestInterceptor implements RequestInterceptor {
//Expensive OAuth2 flow logic
private HashMap<String, String> tokenMap;
public HeaderAuthRequestInterceptor(HashMap<String, String> tokenMap) {
this.tokenMap = tokenMap;
}
#Override
public void apply(RequestTemplate requestTemplate) {
if(tokenMap == null)
return;
requestTemplate.header(key1, tokenMap.get(key1));
requestTemplate.header(key2, tokenMap.get(key2));
....
}
}
And then add the configuration class to your feign client
#FeignClient(name="....",url="...",configuration=FeignCustomHeaderConfig.class)
Reference link here :

Related

Spring Boot instinitate #Bean according to Condition

im working in spring boot project where i want to instantiate a Restemplate Bean with Interceptors , my issue that i don't want to duplicate the code because there is just the header that changes for each conciguration. this is my code :
#Bean
#Qualifier("restTemplateOne")
public RestTemplate restTemplateWithAccessToken() {
return new RestTemplateBuilder()
.interceptors((HttpRequest request, byte[] body, ClientHttpRequestExecution execution) -> {
//this is the only header that i want to add for
request.getHeaders().set("MY_PARTICULAR_HEADER", "my value");
request.getHeaders().set(HttpHeaders.AUTHORIZATION,"my auth value");
return execution.execute(request, body);
}).build();
}
#Bean
#Qualifier("restTemplateTwo")
public RestTemplate restTemplateWithIdToken() {
return new RestTemplateBuilder()
.interceptors((HttpRequest request, byte[] body, ClientHttpRequestExecution execution) -> {
request.getHeaders().set(HttpHeaders.AUTHORIZATION,"my auth value");
return execution.execute(request, body);
}).build();
}
#Autowired
#Qualifier("restTemplateOne")
private RestTemplate restTemplateOne;
#Autowired
#Qualifier("restTemplateTwo")
private RestTemplate restTemplateTwo;
do you have any idea how i can optimize code to avoid duplication . something like adding a parameter to the method and adding the header or not according to the condition.
Thanks in advance.
Just extract and parameterize your interceptor:
#Bean
#Qualifier("restTemplateOne")
public RestTemplate restTemplateWithAccessToken() {
return new RestTemplateBuilder()
.interceptors(new CustomClientHttpRequestInterceptor(true))
.build();
}
#Bean
#Qualifier("restTemplateTwo")
public RestTemplate restTemplateWithIdToken() {
return new RestTemplateBuilder()
.interceptors(new CustomClientHttpRequestInterceptor(false))
.build();
}
private static class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
private boolean needParticularHeader;
public CustomClientHttpRequestInterceptor(boolean needParticularHeader) {
this.needParticularHeader = needParticularHeader;
}
#Override
public ClientHttpResponse intercept(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
if (needParticularHeader) {
//this is the only header that i want to add for
request.getHeaders().set("MY_PARTICULAR_HEADER", "my value");
}
request.getHeaders().set(HttpHeaders.AUTHORIZATION, "my auth value");
return execution.execute(request, body);
}
}

FeignClient RequestParam without URLdecode

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).

spring-data-rest: Validator not being invoked

I am using springboot 2.0.1.RELEASE with spring-data-rest and followed the workaround mentioned here and my Validator is still not being invoked. Here are the details:
ValidatorRegistrar: Workaround for a bug
#Configuration
public class ValidatorRegistrar implements InitializingBean {
private static final List<String> EVENTS;
static {
List<String> events = new ArrayList<String>();
events.add("beforeCreate");
events.add("afterCreate");
events.add("beforeSave");
events.add("afterSave");
events.add("beforeLinkSave");
events.add("afterLinkSave");
events.add("beforeDelete");
events.add("afterDelete");
EVENTS = Collections.unmodifiableList(events);
}
#Autowired
ListableBeanFactory beanFactory;
#Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
#Override
public void afterPropertiesSet() throws Exception {
Map<String, Validator> validators = beanFactory.getBeansOfType(Validator.class);
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
EVENTS.stream().filter(p -> entry.getKey().startsWith(p)).findFirst()
.ifPresent(p -> validatingRepositoryEventListener.addValidator(p, entry.getValue()));
}
}
}
Validator class:
#Component("beforeSaveBidValidator")
public class BeforeSaveBidValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Bid.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Bid bid = (Bid)target;
if (!bid.getAddendaAcknowledged()) {
errors.rejectValue("addendaAcknowledged",
"addendaAcknowledged is not true");
}
}
}
Custom RestController for Bids:
#RestController
#RequestMapping(path = "/bids")
public class BidController {
private BidRepository bidRepository;
#Autowired
public BidController(
BidRepository bidRepository) {
this.bidRepository = bidRepository;
}
#PutMapping("{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
Rest Client Test Code:
Bid bid = new Bid()
...
bid.setAddendaAcknowledged(false)
Map<String, String> uriVariables = new HashMap<String, String>()
uriVariables.put("id", bid.id)
HttpHeaders headers = new HttpHeaders()
headers.setContentType(MediaType.APPLICATION_JSON)
HttpEntity<Bid> entity = new HttpEntity<>(bid, headers)
ResponseEntity<String> response = restTemplate.exchange(
"/bids/{id}", HttpMethod.PUT, entity, Bid.class, bid.id)
// Expected: response.statusCode == HttpStatus.BAD_REQUEST
// Found: response.statusCode == HttpStatus.OK
// Debugger showed that Validator was never invoked.
Any idea what I am missing?
You are trying to use your validator with custom controller, not SDR controller. In this case you can just add it to your controller with #InitBinder annotation:
#RestController
#RequestMapping("/bids")
public class BidController {
//...
#InitBinder("bid") // add this parameter to apply this binder only to request parameters with this name
protected void bidValidator(WebDataBinder binder) {
binder.addValidators(new BidValidator());
}
#PutMapping("/{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
#Component annotation on your validator is not necessary as well as ValidatorRegistrar class.
How to use validators with SDR controllers you can read in my another answer.

Using #Headers with dynamic values in Feign client + Spring Cloud (Brixton RC2)

Is it possible to set dynamic values to a header ?
#FeignClient(name="Simple-Gateway")
interface GatewayClient {
#Headers("X-Auth-Token: {token}")
#RequestMapping(method = RequestMethod.GET, value = "/gateway/test")
String getSessionId(#Param("token") String token);
}
Registering an implementation of RequestInterceptor adds the header but there is no way of setting the header value dynamically
#Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate template) {
template.header("X-Auth-Token", "some_token");
}
};
}
I found the following issue on github and one of the commenters (lpborges) was trying to do something similar using headers in #RequestMapping annotation.
https://github.com/spring-cloud/spring-cloud-netflix/issues/288
Kind Regards
The solution is to use #RequestHeader annotation instead of feign specific annotations
#FeignClient(name="Simple-Gateway")
interface GatewayClient {
#RequestMapping(method = RequestMethod.GET, value = "/gateway/test")
String getSessionId(#RequestHeader("X-Auth-Token") String token);
}
The #RequestHeader did not work for me. What did work was:
#Headers("X-Auth-Token: {access_token}")
#RequestLine("GET /orders/{id}")
Order get(#Param("id") String id, #Param("access_token") String accessToken);
#HeaderMap,#Header and #Param didn't worked for me, below is the solution to use #RequestHeader when there are multiple header parameters to pass using FeignClient
#PostMapping("/api/channelUpdate")
EmployeeDTO updateRecord(
#RequestHeader Map<String, String> headerMap,
#RequestBody RequestDTO request);
code to call the proxy is as below:
Map<String, String> headers = new HashMap<>();
headers.put("channelID", "NET");
headers.put("msgUID", "1234567889");
ResponseDTO response = proxy.updateRecord(headers,requestDTO.getTxnRequest());
I have this example, and I use #HeaderParam instead #RequestHeader :
import rx.Single;
import javax.ws.rs.Consumes;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
#Consumes(MediaType.APPLICATION_JSON)
public interface FeignRepository {
#POST
#Path("/Vehicles")
Single<CarAddResponse> add(#HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, VehicleDto vehicleDto);
}
You can use HttpHeaders.
#PostMapping(path = "${path}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<?> callService(#RequestHeader HttpHeaders headers, #RequestBody Object object);
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "1234");
headers.add("CLIENT_IT", "dummy");
return headers;
}
I use #HeaderMap as it seems very handy if you are working with Open feign. Using this way you can pass header keys and values dynamically.
#Headers({"Content-Type: application/json"})
public interface NotificationClient {
#RequestLine("POST")
String notify(URI uri, #HeaderMap Map<String, Object> headers, NotificationBody body);
}
Now create feign REST client to call the service end point, create your header properties map and pass it in method parameter.
NotificationClient notificationClient = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(customDecoder())
.target(Target.EmptyTarget.create(NotificationClient.class));
Map<String, Object> headers = new HashMap<>();
headers.put("x-api-key", "x-api-value");
ResponseEntity<String> response = notificationClient.notify(new URI("https://stackoverflow.com/example"), headers, new NotificationBody());
A bit late to the game here, but if one needs an enforced, templated value, I discovered that this works in Spring Boot. Apparently, as long as the toString() gives a valid header value, you can use any type.
#FeignClient(
name = "my-feign-client",
url = "http://my-url.com"
)
public interface MyClient {
#GetMapping(
path = "/the/endpoint",
produces = MediaType.APPLICATION_JSON_VALUE
)
DataResponse getData(#RequestHeader(HttpHeaders.AUTHORIZATION) BearerHeader bearerHeader);
final class BearerHeader {
private final String token;
private BearerHeader(String token) {
this.token = token;
}
#Override
public String toString() {
return String.format("Bearer %s", token);
}
public static BearerHeader of(String token) {
return new BearerHeader(token);
}
}

Rest template giving null body and status 302

I am trying to consume a rest call in my mvc controller, however every time I do it returns a null body with http status as 302.Also I am using spring boot with spring security to get https.
I've followed code samples from here: http://websystique.com/springmvc/spring-mvc-4-restful-web-services-crud-example-resttemplate/
and Get list of JSON objects with Spring RestTemplate however none of these work
Can someone please point me in the right direction
Thank you,
REST
#RequestMapping(value = "/api/*")
#RestController
public class PostApiController {
static final Logger logger = LogManager.getLogger(PostApiController.class.getName());
private final PostService postService;
#Inject
public PostApiController(final PostService postService) {
this.postService = postService;
}
//-------------------Retrieve All Posts--------------------------------------------------------
#RequestMapping(value = "post", method = RequestMethod.GET)
public ResponseEntity<List<Post>> getAllPosts() {
List<Post> posts = postService.findAllPosts();
if(posts.isEmpty()){
return new ResponseEntity<List<Post>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
}
return new ResponseEntity<List<Post>>(posts, HttpStatus.OK);
}
}
Controller
#Controller
public class PostController {
static final Logger logger = LogManager.getLogger(PostController.class.getName());
public static final String REST_SERVICE_URI = "http://localhost:8080/api"; //"http://localhost:8080/api";
private final PostService postService;
#Inject
public PostController(final PostService postService) {
this.postService = postService;
}
#SuppressWarnings("unchecked")
#RequestMapping(value = "/getAll")
// public String create(#Valid Post post, BindingResult bindingResult, Model
// model) {
public ModelAndView getAll() {
// if (bindingResult.hasErrors()) {
// return "mvchome";
// }
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<Post>> responseEntity = restTemplate.exchange(REST_SERVICE_URI+"/post",HttpMethod.GET, null, new ParameterizedTypeReference<List<Post>>() {});
// ResponseEntity<Post[]> responseEntity = restTemplate.getForEntity(REST_SERVICE_URI+"/post", Post[].class);
List<Post> postsMap = responseEntity.getBody();
MediaType contentType = responseEntity.getHeaders().getContentType();
HttpStatus statusCode = responseEntity.getStatusCode();
// List<LinkedHashMap<String, Object>> postsMap = restTemplate.getForObject(REST_SERVICE_URI+"/post", List.class);
// String s= REST_SERVICE_URI+"/post";
// logger.info(s);
if(postsMap!=null){
for(Post map : postsMap){
logger.info("User : id="+map.getUid());
}
}else{
logger.info("No user exist----------");
}
//List<Post> postList = postService.findAllPosts();
ModelAndView mav = new ModelAndView("mvchome");
mav.addObject("postsList", postsMap);
Post newpost = new Post();
mav.addObject("post", newpost);
return mav;
}
}
***** to fix my issue I modified my code to just do a redirect on select url paths instead of "/*"
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat =
new TomcatEmbeddedServletContainerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
//used to be just collection.addPattern("/*"); now I changed it to specify which path I want it to redirect
collection.addPattern("/mvchome/*");
collection.addPattern("/home/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(createHttpConnector());
return tomcat;
}
The http status 302 is usually caused by wrong url setting.
First, make sure that public ResponseEntity<List<Post>> getAllPosts() {} method is called (just print List<Post> result inside it).
If it's called properly and you can get the return value inside public ModelAndView getAll() {}.
The problem should be the directing setting of the public ModelAndView getAll() {} method.
Check if you make something wrong in your web.xml or spring configuration. Pay attention to the configuration which redirects to views and the url mapping of your dispatcher servlet.
If public ResponseEntity<List<Post>> getAllPosts() {} is called but you can't get the return value, then it should be the issues of directing setting of the public ResponseEntity<List<Post>> getAllPosts() {} method.
Check your spring configuration and web.xml for that. The possible cause usually will be the misuse of wildcard in the configuration and web.xml, or just unnoticed wrong mapping.

Resources