Spring webclient to make it generic for HTTP methods - spring-boot

I am working on webclient for various HTTP methods (GET,PATCH,POST,DELETE). These are created separately and invoke separately. I am looking to make it as a generic webclient component so minimum changes needs to do in the future. Below is the code for GET and POST. PATCH and Delete is also on similar lines. Please let me know how can I proceed to make generic webclient for various HTTP methods.
#Component
public class HuntsCreateNewCollectionAdapter {
#Autowired
AppProperties properties;
#Autowired
WebclientCollectionConfig webclientCollectionConfig;
public Mono<ResponseEntity<HuntsCollectionDto>> createCollectionAPI(RequestContext context, String title) {
LOGGER.debug("Create Collection API call");
CollectionInputDto collectionInput = CollectionInputDto.builder().title(title).build();
String json = AppJsonUtil.getJsonAsString(collectionInput);
Mono<ResponseEntity<HuntsCollectionDto>> result = webclientCollectionConfig.collectionBuilder().post()
.uri(properties.getCollectionUrl())
.headers(header -> header.addAll(webclientCollectionConfig.getHeaders(context)))
.body(BodyInserters.fromValue(json)).exchangeToMono(response -> {
return response.toEntity(HuntsCollectionDto.class);
});
return result;
}
}
#Component
public class HuntsGetCollectionAdapter {
#Autowired
AppProperties properties;
#Autowired
WebclientCollectionConfig webclientCollectionConfig;
public Mono<ResponseEntity<HuntsCollectionDto>> getCollectionAPI(RequestContext context, String collectionId) {
LOGGER.debug("Get Collection API call");
return webclientCollectionConfig.collectionBuilder().get()
.uri(properties.getCollectionUrl() + "/" + collectionId)
.headers(header -> header.addAll(webclientCollectionConfig.getHeaders(context)))
.exchangeToMono(response -> {
return response.toEntity(HuntsCollectionDto.class);
});
}
}
#Component
public class WebclientCollectionConfig {
#Autowired
AppProperties appProperties;
#Bean
public WebClient collectionBuilder() {
return WebClient.builder().baseUrl(appProperties.getCollectionbaseUrl())
.defaultHeader("Content-Type", "application/json").build();
}
}

Related

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.

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

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 - should I use #Bean or #Component?

Here is the current code at my work.
Method 1
#Configuration
public class AppConfig {
#Bean
#Autowired(required = false)
public HttpClient createHttpClient() {
// do some connections configuration
return new HttpClient();
}
#Bean
#Autowired
public NameClient nameClient(HttpClient httpClient,
#Value("${ServiceUrl:NotConfigured}")
String serviceUrl) {
return new NameClient(httpClient, serviceUrl);
}
}
And the NameClient is a simple POJO looks like following
public class NameClient {
private HttpClient client;
private String url;
public NameClient(HttpClient client, String url) {
this.client = client;
this.url = url;
}
// other methods
}
Instead of using #Bean to configure, I wanted to follow this pattern:
Method 2
#Configuration
public class AppConfig {
#Bean
#Autowired(required = false)
public HttpClient createHttpClient() {
// do some connections configuration
return new HttpClient();
}
}
And use auto-scanning feature to get the bean
#Service //#Component will work too
public class NameClient {
#Autowired
private HttpClient client;
#Value("${ServiceUrl:NotConfigured}")
private String url;
public NameClient() {}
// other methods
}
Why the first method above is used/preferred? What is the advantage of one over the other? I read about the difference between using #Component and #Bean annotations.
They're equivalent.
You would typically use the second one when you own the NameClient class and can thus add Spring annotations in its source code.
You would use the first one when you don't own the NameClient class, and thus can't annotate it with the appropriate Spring annotations.

Resources