Using Spring Boot WebClient to call a dummy api to postman - spring-boot

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.

Related

How can we take the result of `MethodInvokingTaskletAdapter` as a reader in the Spring Batch Step?

How can we take the result of MethodInvokingTaskletAdapter as a reader in the Spring Batch Step? Reference - https://docs.spring.io/spring-batch/docs/current/reference/html/index-single.html#taskletStep and https://github.com/spring-projects/spring-batch/pull/567
Here is the code that I developed
JobConfiguration.java
#Configuration
public class JobConfiguration {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Bean
public CustomService service() {
return new CustomService();
}
#StepScope
#Bean
public MethodInvokingTaskletAdapter methodInvokingTasklet() {
MethodInvokingTaskletAdapter methodInvokingTaskletAdapter = new MethodInvokingTaskletAdapter();
methodInvokingTaskletAdapter.setTargetObject(service());
methodInvokingTaskletAdapter.setTargetMethod("getEmployees");
return methodInvokingTaskletAdapter;
}
#Bean
public Job methodInvokingJob() {
return this.jobBuilderFactory.get("methodInvokingJob")
.start(methodInvokingStep())
.build();
}
#Bean
public Step methodInvokingStep() {
// Looking to configure the Chunk based Step here, dont know how to do using MethodInvokingTaskletAdapter
return this.stepBuilderFactory.get("methodInvokingStep")
.tasklet(methodInvokingTasklet())
.build();
}
}
CustomService.java
public class CustomService {
public void serviceMethod(String message) {
System.out.println(message);
}
public void invokeMethod() {
System.out.println("=============== Your method has executed !");
}
public List<Employee> getEmployees(){
// In real world, it will be an GET API call to XYZ system
List<Employee> employees = new ArrayList<>();
employees.add(Employee.builder().firstName("Ravi").lastName("Shankar").email("ravi.shankar#gmail.com").age(30).build());
employees.add(Employee.builder().firstName("Parag").lastName("Rane").email("parag.rane#gmail.com").age(11).build());
employees.add(Employee.builder().firstName("Priya").lastName("Pande").email("priya.pande#gmail.com").age(40).build());
employees.add(Employee.builder().firstName("Kiran").lastName("khot").email("kiran.khot#gmail.com").age(50).build());
return employees;
}
}
Employee.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Employee {
private String firstName;
private String lastName;
private String email;
private int age;
}
MethodInvokingTaskletApplication.java
#EnableBatchProcessing
#SpringBootApplication
public class MethodInvokingTaskletApplication {
public static void main(String[] args) {
SpringApplication.run(MethodInvokingTaskletApplication.class, args);
}
}
To answer your question, you can't. The MethodInvokingTaskletAdapter is meant to adapt a POJO to a Tasklet. We have an ItemReaderAdapter that you can use to adapt a POJO to an ItemReader. You can read about it in the documentation here: https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/item/adapter/ItemReaderAdapter.html
Now you'll have an issue with your service as configured in that each call to the delegating POJO is considered an item. That means that your item as configured will be a List<Employee> instead of just an Employee. Given your configuration states it's not the real service, I'll assume that your real service should return an Employee per call and null once the results are exhausted.
To update your configuration (with your service as it is configured in your question) in your sample:
...
#StepScope
#Bean
public ItemReaderAdapter itemReader() {
ItemReaderAdapter reader = new ItemReaderAdapter();
reader.setTargetObject(service());
reader.setTargetMethod("getEmployees");
return reader;
}
#Bean
public Job methodInvokingJob() {
return this.jobBuilderFactory.get("methodInvokingJob")
.start(methodInvokingStep())
.build();
}
#Bean
public Step methodInvokingStep() {
return this.stepBuilderFactory.get("methodInvokingStep")
.<List<Employee>, List<Employee>>chunk(methodInvokingTasklet())
.reader(itemReader())
// You'll need to define a writer...
.writer(itemWriter())
.build();
}
...

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.

GET turning into POST with Spring Feign

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.

Spring, webflux: The getRemoteAddress method of the ServerHttpRequest object returns null when request performed from WebTestClient

I have a controller
#RestController
public class NameController {
#Autowired
private NameService nameService;
#GetMapping("/name")
public Mono<UploadParamsDto> getName(ServerHttpRequest request) {
return nameService.getNameByHost(request.getRemoteAddress().getHostName());
}
}
and i have a test method:
#ExtendWith(SpringExtension.class)
#WebFluxTest(NameControllerTest.class)
#ActiveProfiles("test")
class NameControllerTest {
#Autowired
private WebTestClient webClient;
#Test
void nameTest() {
webClient.get().uri("/name")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk();
}
}
When I run the test in order to check my getName method i got NPE because
request.getRemoteAddress() returns null, could you please tell me how to mock ServerHttpRequest? (I know that there is MockServerHttpRequest, but I couldn't managed with it)
My purpose is make request.getRemoteAddress().getHostName() return mock value.
Thanks to everyone.
Works in next way:
#ExtendWith(SpringExtension.class)
#WebFluxTest(NameControllerTest.class)
#ActiveProfiles("test")
class NameControllerTest {
#Autowired
private ApplicationContext context;
#Test
void nameTest() {
WebTestClient webClient = WebTestClient
.bindToApplicationContext(context)
.webFilter(new SetRemoteAddressWebFilter("127.0.0.1"))
.configureClient()
.build();
webClient.get().uri("/name")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk();
}
}
Where SetRemoteAddressWebFilter is WebFilter:
public class SetRemoteAddressWebFilter implements WebFilter {
private String host;
public SetRemoteAddressWebFilter(String host) {
this.host = host;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(decorate(exchange));
}
private ServerWebExchange decorate(ServerWebExchange exchange) {
final ServerHttpRequest decorated = new ServerHttpRequestDecorator(exchange.getRequest()) {
#Override
public InetSocketAddress getRemoteAddress() {
return new InetSocketAddress(host, 80);
}
};
return new ServerWebExchangeDecorator(exchange) {
#Override
public ServerHttpRequest getRequest() {
return decorated;
}
};
}
}
Running a test with #WebFluxTest doesn't involve a real server, you've figured that out.
But getting a NullPointerException doesn't feel right still - could you create an issue on https://jira.spring.io about that? I don't think you should have to work around this, but Spring Framework should probably provide some infrastructure to "mock" that information.

Returing Hystrix AsyncResult from Spring Boot Controller

I have the following Spring Boot controller:
#Controller
public class TestController {
#Autowired
private TestService service;
#GetMapping(path="/hello")
public ResponseEntity<String> handleGet() {
return service.getResponse();
}
#GetMapping(path="/hello/hystrix")
public Future<ResponseEntity<String>> handleGetAsync() {
return service.getResponseAsync();
}
#GetMapping(path="/hello/cf")
public Future<ResponseEntity<String>> handleGetCF() {
return service.getResponseCF();
}
}
and service:
#Service
public class TestService {
#HystrixCommand
public ResponseEntity<String> getResponse() {
ResponseEntity<String> response = ResponseEntity.status(HttpStatus.OK).body("Hello");
return response;
}
#HystrixCommand
public Future<ResponseEntity<String>> getResponseAsync() {
return new AsyncResult<ResponseEntity<String>>() {
#Override
public ResponseEntity<String> invoke() {
return getResponse();
}
};
}
public Future<ResponseEntity<String>> getResponseCF() {
return CompletableFuture.supplyAsync(() -> getResponse());
}
}
and application:
#EnableHystrix
#SpringBootApplication
#EnableAsync
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
}
When I hit the /hello/cf endpoint, I get a response "Hello"
When I hit the /hello/hystrix endpoint, I get a 404 error.
Am I able to return an AsyncResult from a controller in this manner? If so, what am I doing wrong?
Thanks.
Your service class needs to return a CompletableFuture.
Also, unless you are using AspectJ, the circuit breaker will not work if the method with #HystrixCommand is called from within the same class.

Resources