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

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);
}
}

Related

403 response with Request Interceptors Feing client

I have a request interceptor config for my feign client that i will like to verify if it is configured properly. It is suppose to make request to the auth url and get a authorization taken.
This seems to work fine. But i think its not putting it to every request sent to to the resource server. Hence i keep getting 403. but when i try this on postman with the auth token generated in my code it works fine.
Bellow is the code
#Component
public class FeignC2aSystemOAuthInterceptor implements RequestInterceptor {
#Value("${c2a.oauth2.clientId}")
private String clientId;
#Value("${c2a_system.authUrl}")
private String authUrl;
#Value("${c2a.oauth2.clientSecret}")
private String clientSecret;
private String jwt;
private LocalDateTime expirationDate = LocalDateTime.now();
private final RestTemplate restTemplate;
public FeignC2aSystemOAuthInterceptor(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public void apply(RequestTemplate requestTemplate) {
if (LocalDateTime.now().isAfter(expirationDate)) {
requestToken();
System.out.println("JUST AFTER REQUEST" + this.jwt);
}
/* use the token */
System.out.println("USE THE TOKEN" + this.jwt);
requestTemplate.header("Authorization: Bearer " + this.jwt);
}
private void requestToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("client_id", clientId);
map.add("client_secret", clientSecret);
map.add("grant_type", "client_credentials");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<C2AAuthResponse> response = restTemplate.postForEntity(authUrl, request, C2AAuthResponse.class);
this.jwt = Objects.requireNonNull(response.getBody()).getAccessToken();
LocalDateTime localDateTime = LocalDateTime.now();
this.expirationDate = localDateTime.plusSeconds(response.getBody().getExpiresIn());
}
config
#Configuration
public class FeignC2aSystemConfig {
#Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
};
#Bean
FeignC2aSystemOAuthInterceptor fen () {
return new FeignC2aSystemOAuthInterceptor(getRestTemplate());
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
and client
#FeignClient(name = "c2aSystem", url = "${c2a_system.base_url}", configuration = FeignC2aSystemConfig.class)
public interface C2AApiClient {
#PostMapping(value = C2ASystemIntegrationUrls.SEND, produces = "application/json", consumes = "application/json")
HttpServletResponse sendSms(#RequestBody C2aMessage c2aMessage);
#GetMapping(value = C2ASystemIntegrationUrls.GETLIST, produces = "application/json", consumes = "application/json")
List<MessageData> getMessages();
}
during logging i have noticed that it i call the interceptor and i can see the auth token logged using sout.
Please i would like to know if i have made a mess somewhere along the way that might cause it not to apply the authorization token to the request, thanks
You're using the RequestTemplate API wrong in this line:
requestTemplate.header("Authorization: Bearer " + this.jwt);
the header method accepts 2 parameters. First a key and then the corresponding value, and there's an overload with a String vararg. Your code will complile because of the varag parameter but won't work because it'll be handled as an empty array argument.
The implementation in the RequestTemplate is clear. If the array is empty, it'll consider that header for removal.
The fix is easy, just put the JWT token into the second argument instead of concatenating it with the header key, like this:
requestTemplate.header("Authorization: Bearer ", this.jwt);

RestTemplate get with body

How to make get with body using rest template?
Based on question from: POST request via RestTemplate in JSON, I tried make GET with body via HttpEntity (just check if it is possible), but
it failed receiving:
Required request body is missing
For HttpMethod.POST: localhost:8080/test/post body is added correctly, but for
HttpMethod.GET localhost:8080/test/get it is not mapped.
My code is, as below:
#RestController
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
private final RestTemplate restTemplate = new RestTemplate();
#GetMapping("/test/{api}")
public SomeObject test(#PathVariable("api") String api) {
String input = "{\"value\":\"ok\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(input, headers);
HttpMethod method = "get".equals(api) ? HttpMethod.GET : HttpMethod.POST;
String url = "http://localhost:8080/" + api;
return restTemplate.exchange(url, method, entity, SomeObject.class).getBody();
}
#GetMapping("/get")
public SomeObject getTestApi(#RequestBody(required = false) SomeObject someObject) {
return new SomeObject() {{ setValue(someObject != null ? "ok" : "error"); }};
}
#PostMapping("/post")
public SomeObject postTestApi(#RequestBody(required = false) SomeObject someObject) {
return new SomeObject() {{ setValue(someObject != null ? "ok" : "error"); }};
}
#Data
public static class SomeObject {
private String value;
}
}
Here is the repo with full example: https://gitlab.com/bartekwichowski/git-with-body
I wonder, what is wrong with code?
Also accorging to: HTTP GET with request body
GET with body is possible, but just not good practice.
I found this can't remeber where. Not a good practice, but if in your enviroment you have no other chance:
private static final class HttpComponentsClientHttpRequestWithBodyFactory extends HttpComponentsClientHttpRequestFactory {
#Override
protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
if (httpMethod == HttpMethod.GET) {
return new HttpGetRequestWithEntity(uri);
}
return super.createHttpUriRequest(httpMethod, uri);
}
}
private static final class HttpGetRequestWithEntity extends HttpEntityEnclosingRequestBase {
public HttpGetRequestWithEntity(final URI uri) {
super.setURI(uri);
}
#Override
public String getMethod() {
return HttpMethod.GET.name();
}
}
and when you get your restTemplate object
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestWithBodyFactory());
i had the same issue with RestTemplate and GET.
Tried to switch to Unirest but that also did not allow to use body with GET method.
Changing GET to POST is successful.
Making a call from postman after deploying in Liberty works fine and body did get accepted and expected response is generated.
i believe its something with the embedded tomcat server used.

How to pass multiple RequestHeader when using #FeignClient

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 :

spring boot setContentType is not working

I'm trying to return an image on spring-boot (1.2.2)
How should I set the content-type?
Non of the following are working for me (meaning that response headers are not containing 'content-type' header at all ):
#RequestMapping(value = "/files2/{file_name:.+}", method = RequestMethod.GET)
public ResponseEntity<InputStreamResource> getFile2(final HttpServletResponse response) throws IOException {
InputStream is = //someInputStream...
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.setContentType("image/jpeg");
InputStreamResource inputStreamR = new InputStreamResource(is);
return new ResponseEntity<>(inputStreamR, HttpStatus.OK);
}
#RequestMapping(value = "/files3/{file_name:.+}", method = RequestMethod.GET)
public HttpEntity<byte[]> getFile3() throws IOException {
InputStream is = //someInputStream...
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
return new HttpEntity<>(IOUtils.toByteArray(is), headers);
}
Firstly, you'll need to apply the #ResponseBody annotation in addition to #RequestMapping, unless you are using #RestController at the class level instead of just #Controller. Also, try the produces element of #RequestMapping e.g.
#RequestMapping(value = "/files2/{file_name:.+}", method = RequestMethod.GET, produces = {MediaType.IMAGE_JPEG_VALUE})
This should 'narrow the primary mapping' and ensure the correct content type is set. See the docs: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-requestmapping-produces
Got it... Had to add ByteArrayHttpMessageConverter to WebConfiguration class:
#Configuration
#EnableWebMvc
#ComponentScan
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
httpMessageConverters.add(new ByteArrayHttpMessageConverter());
}
}
And the then my second attempt (getFile3()) was working correctly

How to access plain json body in Spring rest controller?

Having the following code:
#RequestMapping(value = "/greeting", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String greetingJson(#RequestBody String json) {
System.out.println("json = " + json); // TODO json is null... how to retrieve plain json body?
return "Hello World!";
}
The String json argument is always null despite json being sent in the body.
Note that I don't want automatic type conversion, I just want the plain json result.
This for example works:
#RequestMapping(value = "/greeting", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String greetingJson(#RequestBody User user) {
return String.format("Hello %s!", user);
}
Probably I can use the use the ServletRequest or InputStream as argument to retrieve the actual body, but I wonder if there is an easier way?
Best way I found until now is:
#RequestMapping(value = "/greeting", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String greetingJson(HttpEntity<String> httpEntity) {
String json = httpEntity.getBody();
// json contains the plain json string
Let me know if there are other alternatives.
You can just use
#RequestBody String pBody
Only HttpServletRequest worked for me. HttpEntity gave null string.
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
#RequestMapping(value = "/greeting", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#ResponseBody
public String greetingJson(HttpServletRequest request) throws IOException {
final String json = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
System.out.println("json = " + json);
return "Hello World!";
}
simplest way that works for me is
#RequestMapping(value = "/greeting", method = POST, consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public String greetingJson(String raw) {
System.out.println("json = " + raw);
return "OK";
}
If you have dozens of Methods that need to get HTTP body as JSON and convert it to custom data type, it is a better way to implement the support on the framework
public static class Data {
private String foo;
private String bar;
}
//convert http body to Data object.
//you can also use String parameter type to get the raw json text.
#RequestMapping(value = "/greeting")
#ResponseBody
public String greetingJson(#JsonBody Data data) {
System.out.println(data);
return "OK";
}
notice that we using user defined annotation #JsonBody.
// define custom annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface JsonBody {
String encoding() default "utf-8";
}
//annotation processor for JsonBody
#Slf4j
public class JsonBodyArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(JsonBody.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
JsonBody annotation = parameter.getParameterAnnotation(JsonBody.class);
assert annotation != null;
ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class);
if (servletRequest == null) {
throw new Exception("can not get ServletRequest from NativeWebRequest");
}
String copy = StreamUtils.copyToString(servletRequest.getInputStream(), Charset.forName(annotation.encoding()));
return new Gson().fromJson(copy, parameter.getGenericParameterType());
}
}
// register the annotation processor
#Component
public class WebConfig implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new JsonBodyArgumentResolver());
}
}
As of 4.1 you can now use RequestEntity<String> requestEntity and access the body by requestEntity.getBody()
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/RequestEntity.html

Resources