GET turning into POST with Spring Feign - spring

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.

Related

Vaadin + Spring Boot returns 403 Forbidden error on PUT, POST, DELETE requests

Implementing a simple web application using REST Api using Spring Boot + Vaadin. Also, Security is connected in the project, a simple login with a login-password is carried out. Get() requests work fine, but a 403 "Forbidden" error occurs on PUT, POST, DELETE requests.
I tried disabling csrf using the http.httpBasic().and().csrf().disable() method, it does not help, and this is not recommended in production either.
I also tried adding to antMatchers() specifically a request type like this: http.httpBasic().and().authorizeRequests().antMatchers(HttpMethod.POST,"/**").permitAll(), also not helps.
Configuration class:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends VaadinWebSecurity {
private static class SimpleInMemoryUserDetailsManager extends InMemoryUserDetailsManager {
public SimpleInMemoryUserDetailsManager() {
createUser(Manager.withUsername("manager1")
.password("{noop}123")
.roles(ROLE_MANAGER)
.build());
createUser(Manager.withUsername("manager2")
.password("{noop}123")
.roles(ROLE_MANAGER)
.build());
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("/enterprises/\*\*").hasRole(ROLE_MANAGER);
super.configure(http);
setLoginView(http, LoginView.class);
}
#Bean
public InMemoryUserDetailsManager enterprisesService() {
return new SimpleInMemoryUserDetailsManager();
}
}
Rest-controller:
#org.springframework.web.bind.annotation.RestController
#RequestMapping(path = "/")
public class RestController {
#Autowired
private VehiclesRepository vehiclesRepository;
#Autowired
private EnterprisesRepository enterprisesRepository;
#Autowired
private DriversRepository driversRepository;
#Autowired
private ManagersRepository managersRepository;
#GetMapping(
path = "/vehicles",
produces = "application/json")
public VehiclesDto getVehicles() {
VehiclesDto vehiclesDto = new VehiclesDto();
for (Vehicle vehicle : vehiclesRepository.findAll()) {
vehiclesDto.getVehicles().add(vehicle);
}
return vehiclesDto;
}
#GetMapping(
path = "/enterprises",
produces = "application/json")
public #ResponseBody EnterprisesDto getEnterprises(#RequestParam("managerId") String managerId) {
Manager manager = null;
for (Manager managerFromRepo : managersRepository.findAll()) {
if (managerFromRepo.getId().equals(Long.parseLong(managerId))) {
manager = managerFromRepo;
break;
}
}
EnterprisesDto enterprisesDto = new EnterprisesDto();
if (manager == null) return enterprisesDto;
for (Enterprise enterprise : enterprisesRepository.findAll()) {
if (manager.getEnterprises().contains(enterprise.getId()))
enterprisesDto.getEnterprises().add(enterprise);
}
return enterprisesDto;
}
#GetMapping(
path = "/drivers",
produces = "application/json")
public DriversDto getDrivers() {
DriversDto driversDto = new DriversDto();
for (Driver driver : driversRepository.findAll()) {
driversDto.getDrivers().add(driver);
}
return driversDto;
}
#PostMapping("/createVehicle")
public #ResponseBody String createVehicle(#RequestBody String info) {
return "it works!!!";
}
#DeleteMapping("/deleteVehicle")
public #ResponseBody String deleteVehicle(){
return "it works!!!";
}
}
Testing requests through Postman using Basic Authentication.
You can disable CSRF just for your API:
http.csrf().ignoringRequestMatchers(new AntPathRequestMatcher("/enterprises/**"));

Using Spring Boot WebClient to call a dummy api to postman

I am missing something here. I am attempting to pull information using Spring Boot WebClient from a Dummy Api that's an Http request. I am not getting any info pulled when I go into postman.
Thanks for any insight you can give me. I am still very new to coding and self-taught.
Here's my employee controller:
#Autowired
WebClientApp webClientApp;
#GetMapping("/consume")
public String getEmployee(Model model) {
model.addAttribute("listEmployees", empServiceImpl.getAllEmployees());
model.addAttribute("listemps", webClientApp.webClientBuilder());
return "index";
}
Web Client
private WebClient webClient;
public void SimpleWebClient(WebClient webClient) {
this.webClient = webClient;
}
public Flux<Employee> webClientBuilder() {
return this.webClient
//this.webClientBuilder = webClientBuilder.baseUrl(DummyEmployee)
.get()
.uri("api/v1/employees")
.retrieve()
.bodyToFlux(Employee.class);
}
Employee
#Data
#ToString
//#AllArgsConstructor
//#NoArgsConstructor
#JsonRootName(value = "data")
public class Employee {
#JsonProperty("id")
public int employeeID;
#JsonProperty("employee_name")
public String employeeName;
#JsonProperty("employee_salary")
public String employeeSalary;
#JsonProperty("employee_age")
public int employeeAge;
#JsonProperty("employee_image")
public Blob employeeImage;
}
Service
#Repository
#ComponentScan(basePackages = {"com.example.app.repository"})
#Service
public class ServiceImpl implements EmpService{
#Autowired
private EmployeeRepository employeeRepo;
#SuppressWarnings("unchecked")
public List<Employee> getAllEmployees() {
return (List<Employee>) employeeRepo.findAll();
}
}
Service
#Service
public interface EmpService {
static List<Employee> getAllEmployees() {
// TODO Auto-generated method stub
return null;
}
}
Main
public static void main(String[] args) {
SpringApplication.run(RestWebsiteDataProjectApplication.class, args);
}
#Bean
public WebClient webClientFromScratch() {
return WebClient.builder()
.baseUrl("https://dummy.restapiexample.com/")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
Flux only emits its content when it is subscribed. You are not subscribing to the Flux returned by the webClientBuilder() method.
You shouldn't really do this, but try adding .block() to your Controller as follows:
#Autowired
WebClientApp webClientApp;
#GetMapping("/consume")
public String getEmployee(Model model) {
model.addAttribute("listEmployees", empServiceImpl.getAllEmployees());
model.addAttribute("listemps", webClientApp.webClientBuilder().block());
return "index";
}
If this works, please consider reworking your code because while working with Spring WebFlux (reactive programming) you should always deal with Mono and Flux so that you can take full advantage of the reactive stack.

How do I spring cloud gateway custom filter e2e test?

I have implemented custom GatewayFilterFactory filter. But I don't know how to test this filter with e2e setup.
I have referenced official spring-cloud-gateway AddRequestHeaderGatewayFilterFactoryTests test case code.
This is my custom filter code:
#Component
public class MyCustomFilter implements GatewayFilterFactory<MyCustomFilter.Config>, Ordered {
#Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter((this::filter), getOrder());
}
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/* do some filtering */
}
#Override
public int getOrder() {
return 1000;
}
#Override
public Config newConfig() {
return new Config(MyCustomFilter.class.getSimpleName());
}
public static getConfig() {
return
}
#Getter
#Setter
public static class Config {
private String name;
Config(String name) {
this.name = name;
}
}
}
And this is my test code:
BaseWebClientTests class look exactly the same as official BaseWebClientTests class code
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#DirtiesContext
#ActiveProfiles("my-custom-filter")
public class MyCustomFilterTests extends BaseWebClientTests {
#LocalServerPort
protected int port = 0;
protected WebTestClient testClient;
protected WebClient webClient;
protected String baseUri;
#Before
public void setup() throws Exception {
setup(new ReactorClientHttpConnector(), "http://localhost:" + port);
}
protected void setup(ClientHttpConnector httpConnector, String baseUri) {
this.baseUri = baseUri;
this.webClient = WebClient.builder().clientConnector(httpConnector)
.baseUrl(this.baseUri).build();
this.testClient = WebTestClient
.bindToServer(httpConnector)
.baseUrl(this.baseUri)
.build();
}
#Test
public void shouldFailByFilterTests() {
/* This test should be failed but success :( */
testClient.get().uri("/api/path")
.exchange().expectBody(Map.class).consumeWith(result -> {
/* do assertion */
});
}
#EnableAutoConfiguration
#SpringBootConfiguration
#Import(DefaultTestConfig.class)
public static class TestConfig {
#Value("${test.uri}")
String uri;
#Bean
public MyCustomFilter myCustomFilter() {
return new MyCustomFilter();
}
#Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder, MyCustomFilter myCustomFilter) {
return builder.routes().route("my_custom_filter",
r -> r.path("/api/path")
.filters(f -> f.filter(myCustomFilter.apply(new MyCustomFilter.Config("STRING"))))
.uri(uri))
.build();
}
}
}
Lastly Target controller looks like this:
#RestController
#RequestMapping("/api/path")
public class HttpBinCompatibleController {
#GetMapping("/")
public Mono<BodyData> identity() {
return Mono.just(new BodyData("api success"));
}
#NoArgsConstructor
#AllArgsConstructor
#Getter
static class BodyData {
private String message;
}
}
What I understand how this filter factory test code works is that
custom filter: custom filter is setup inside TestConfig class testRouteLocator method
target controller: target controller is defined as HttpBinCompatibleController class
testClient sends the request, and custom should do some filtering, then target controller should receive the request from testClient.
What I expect from this shouldFailByFilterTests TC is that before request from testClient is sent to target controller, that request should be rejected by MyCustomFilter. But the request is sent to the target controller.
I think the request from testClient is not proxied by testRouteLocator but I'm not sure
Question
What is the cause of this problem?
Is there another way to test my own custom filter?
This problem was related to the version incompatibility between Spring Boot and Spring Cloud.
I was using Spring Boot version 2.1.7 and Spring Cloud version Greenwich.SR2.
Then I found this 'Release train Spring Boot compatibility' table on this link
Before I've noticed version incompatibility, for using #Configuration(proxyBeanMethods = false) feature, upgraded Spring Boot version to 2.2.x.
The solution is using 2.1.x branch BaseWebClientTests class.

How to dynamically set #Bean at #Configuration in #Service?

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!

How can I adjust load balancing rule by feign in spring cloud

As I know, feign include ribbon's function, and I prove it in my code.
When I use feign, the default rule is Round Robin Rule.
But how can I change the rule in my feign client code, is ribbon the only way?
Here is my code below, so please help.
ConsumerApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableFeignClients
#EnableCircuitBreaker
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
UserFeignClient .java
#FeignClient(name = "cloud-provider", fallback = UserFeignClient.HystrixClientFallback.class)
public interface UserFeignClient {
#RequestMapping("/{id}")
BaseResponse findByIdFeign(#RequestParam("id") Long id);
#RequestMapping("/add")
BaseResponse addUserFeign(UserVo userVo);
#Component
class HystrixClientFallback implements UserFeignClient {
private static final Logger LOGGER = LoggerFactory.getLogger(HystrixClientFallback.class);
#Override
public BaseResponse findByIdFeign(#RequestParam("id") Long id) {
BaseResponse response = new BaseResponse();
response.setMessage("disable");
return response;
}
#Override
public BaseResponse addUserFeign(UserVo userVo) {
BaseResponse response = new BaseResponse();
response.setMessage("disable");
return response;
}
}
}
FeignController.java
#RestController
public class FeignController {
#Autowired
private UserFeignClient userFeignClient;
#GetMapping("feign/{id}")
public BaseResponse<Date> findByIdFeign(#PathVariable Long id) {
BaseResponse response = this.userFeignClient.findByIdFeign(id);
return response;
}
#GetMapping("feign/user/add")
public BaseResponse<Date> addUser() {
UserVo userVo = new UserVo();
userVo.setAge(19);
userVo.setId(12345L);
userVo.setUsername("nick name");
BaseResponse response = this.userFeignClient.addUserFeign(userVo);
return response;
}
}
From the documentation:
#RibbonClient(name = "cloud-provider", configuration = CloudProviderConfiguration.class)
public class ConsumerApplication {
/* ... */
}
class CloudProviderConfiguration {
#Bean
public IRule ribbonRule(IClientConfig config) {
return new RandomRule();
}
}

Resources