How do I make a post / get request to a endpoint with a requestHeader? - spring

Method in question
#GetMapping("/all")
public Mono<ResponseEntity<String>> getSomeData(#RequestHeader String someId) {
...some code
}
Tried to call the consume the endpoint with this method:
#Autowired
WebClient.Builder webClient;
String someString = webClient.
.get()
.uri(someUrl)
.header("someId", "someString")
.retrieve()
.bodyToMono(String.class)
.block();
I got a status 415 with Unsupported media type with "Content type '' not supported"
How do I use webClientBuilder to set my id header?

You just need to set the correct content-type. If your controller expects it to be "plain/text" you might have to set that explicitly within your requesting client. 415 does indicate a miss match.

As mentioned by #Alex you are autowiring builder instead look for the concrete implementation of WebClient. Please check my WebClient config bean. But that is not the actual issue.
When you are sending body with webClient you have to use
.body(...)
so for sending plain text body where controller is expecting plain body you need something like below:
.body(BodyInserters.fromProducer(Mono.just("random body"), String.class))
and when controller is expecing an object is request you need to use something like this
.body(BodyInserters.fromProducer(Mono.just(new Greet("Hello there this is the body of post request")), Greet.class))
Greet.java
public static class Greet {
String name;
public Greet() {
}
public Greet(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Configuration of WebCLient
#Configuration
class WebClientConfig {
#Bean
WebClient webClient() {
return WebClient.builder().baseUrl("http://localhost:8080/").build();
}
}
#RequestMapping("/sample")
#RestController
static class SampleComntroller {
private final WebClient webClient;
#Autowired
SampleComntroller(WebClient webClient) {
this.webClient = webClient;
}
#GetMapping(value = "/main-get")//, consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<String> helloGet(#RequestHeader(name = "someId") String someId) {
return Mono.just("Hello, Spring!, get, response with header is=>" + someId);
}
#PostMapping(value = "/main-post-plain-string", consumes = MediaType.TEXT_PLAIN_VALUE)
public Mono<String> helloPost(#RequestHeader(name = "someId") String someId, #RequestBody String body) {
return Mono.just("Hello, Spring!, post, response with header is=>" + someId + " and random body " + UUID.randomUUID().toString());
}
#PostMapping(value = "/main-post-object", consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<String> helloPostObject(#RequestHeader(name = "someId") String someId, #RequestBody Greet greet) {
return Mono.just("Hello, Spring!, post, response with header is=>" + someId + " " + greet.getName() + " " + UUID.randomUUID().toString());
}
#GetMapping("/delegate-get")
public String delegateGet() {
return webClient
.get()
.uri("/sample/main-get")
.header("someId", "178A-0E88-get")
.retrieve().bodyToMono(String.class).block();
}
#PostMapping("/delegate-post")
public String delegatePost() {
return webClient
.post()
.uri("/sample/main-post-plain-string")
.body(BodyInserters.fromProducer(Mono.just("random body"), String.class))
.header("someId", "178A-0E88-post")
.retrieve()
.bodyToMono(String.class).block();
}
#PostMapping("/delegate-post-object")
public String delegatePostObject() {
return webClient
.post()
.uri("/sample/main-post-object")
.body(BodyInserters.fromProducer(Mono.just(new Greet("Hello there this is the body of post request")), Greet.class))
.header("someId", "178A-0E88-post")
.retrieve()
.bodyToMono(String.class).block();
}
}

Related

Spring WebClient : Implement Fallback method

I want to call my fall-back API when my actual API is taking more than 1 second
#GetMapping("/{id}")
public String getDetailsById(#PathVariable Long id) {
var url = getAPIUrl(id);
var response = webClient.get()
.uri(url)
.retrieve()
.onStatus(HttpStatus::isError,this::myFallBackMethod)
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(1))
.block();
return response;
}
private Mono<? extends Throwable> myFallBackMethod(ClientResponse clientResponse) {
return Mono.just("");
}
I get two compile exceptions
Incompatible types
and
cannot resolve methoe myFallBackMethod
How to handle fall backs and return the String ?
I was able to do that my calling the function onErrorResume
#GetMapping("/{id}")
public String getDetailsById(#PathVariable Long id) {
var url = getAPIUrl(id);
var response = webClient.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(1))
.onErrorResume(throwable -> myFallBackMethod(id,throwable))
.block();
return response;
}
private Mono<? extends String> myFallBackMethod(Long id, Throwable throwable) {
return Mono.just("test sample");
}

Using WebClient in a controller tested with #WebFluxTests throws java.lang.IllegalArgumentException: URI is not absolute

I have a #RestController that uses WebClient in one of its endpoints to invoke another endpoint from the same controller:
#RestController
#RequestMapping("/api")
#RequiredArgsConstructor
public class FooRestController {
private final WebClient webClient;
#Value("${service.base-url}")
private String fooServiceBaseUrl;
#GetMapping(value = "/v1/foo", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Foo> getFoo() {
return webClient.get().uri(fooServiceBaseUrl + "/api/v1/fooAnother")
.retrieve()
.bodyToFlux(Foo.class);
}
#GetMapping(value = "/v1/fooAnother", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Foo> getFooAnother() {
return Flux.xxx
}
In my #WebFluxTests class I can test the fooAnother endpoint without any problem:
#ExtendWith(SpringExtension.class)
#Import({MyWebClientAutoConfiguration.class})
#WebFluxTest(FooRestController.class)
class FooRestControllerTest {
#Test
void shouldGetFooAnother() {
xxx
webTestClient.get()
.uri("/api/v1/fooAnother")
.exchange()
.expectStatus().isOk()
}
#Test
void shouldGetFoo() {
xxx
webTestClient.get()
.uri("/api/v1/fooAnother")
.exchange()
.expectStatus().isOk()
}
However when I test the /v1/foo endpoint (notice in my tests service.base-url=""), it fails calling webClient.get().uri(fooServiceBaseUrl + "/api/v1/fooAnother") having fooServiceBaseUrl + "/api/v1/fooAnother" = "/api/v1/fooAnother", complaining that it need an absolute URL: java.lang.IllegalArgumentException: URI is not absolute: /api/v1/fooAnother.
How could I fix this test?
You have to configure your WebClient using WebClient.Builder(). You could do this inside your FooRestController but I like to use Configuration that way if you have any further WebClient customizations, you could do in different class rather than in your controller class.
Configure WebClient:
#Configuration
public class WebClientConfig() {
#Value("${service.base-url}")
private String fooServiceBaseUrl;
#Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.baseUrl(fooServiceBaseUrl)
.build();
}
}
If you decide to go ahead with configuring your webClient in your FooRestController, you have to refactor as below. You don't need above configuration.
If this doesn't solve your issue, you might have some sort of mismatch between application.yml file and the value that your are trying to inject in fooServiceBaseUrl.
#RestController
#RequestMapping("/api")
public class FooRestController() {
private final WebClient webClient;
#Value("${service.base-url}")
private String fooServiceBaseUrl;
public FooRestController(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder
.baseUrl(fooServiceBaseUrl)
.build();
}
#GetMapping(value = "/v1/foo", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Foo> getFoo() {
return webClient
.get()
.uri("/api/v1/fooAnother")
.retrieve()
.bodyToFlux(Foo.class);
}
#GetMapping(value = "/v1/fooAnother", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Foo> getFooAnother() {
return Flux.xxx
}
}

Spring WebFlux : WebClient + Fallback on error

I want to set up a fallback when my program works if the first service is unavailable.
On the second service, I use WebClient which accesses the first service and receives data from it.
I made two options but they do not work for me.
If both services are alive, then everything works well.
If the first service is unavailable, then when I try to send a request via WebClient, nothing happens, I see a blank screen in the browser.
1) The first option:
#Service
public class WebClientService {
private static final String API_MIME_TYPE = "application/json";
private static final String API_BASE_URL = "http://localhost:8081";
private static final String USER_AGENT = "User Service";
private static final Logger logger = LoggerFactory.getLogger(WebClientService.class);
private WebClient webClient;
public WebClientService() {
this.webClient = WebClient.builder()
.baseUrl(API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, API_MIME_TYPE)
.defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
.filter(WebClientService.errorHandlingFilter())
.build();
}
public Flux<Bucket> getDataByWebClient() {
return webClient
.get()
.uri("/getAll")
.exchange()
.flatMapMany(clientResponse -> clientResponse.bodyToFlux(Bucket.class));
}
public static ExchangeFilterFunction errorHandlingFilter() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if(clientResponse.statusCode()!=null && (clientResponse.statusCode().is5xxServerError() || clientResponse.statusCode().is4xxClientError()) ) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> {
return Mono.error(new MyCustomServerException());
});
}else {
return Mono.just(clientResponse);
}
});
}
}
Class MyCustomServerException
public class MyCustomServerException extends Throwable {
public String getAllEmployeesList() {
return "Server error";
}
public MyCustomServerException() {
getAllEmployeesList();
}
}
2) The second option:
#Service
public class WebClientService {
private static final String API_MIME_TYPE = "application/json";
private static final String API_BASE_URL = "http://localhost:8081";
private static final String USER_AGENT = "User Service";
private static final Logger logger = LoggerFactory.getLogger(WebClientService.class);
private WebClient webClient;
public WebClientService() {
this.webClient = WebClient.builder()
.baseUrl(API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, API_MIME_TYPE)
.defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
.build();
}
public Flux<Bucket> getDataByWebClient() {
return webClient
.get()
.uri("/stream/buckets/delay")
.exchange()
.flatMapMany(clientResponse -> clientResponse.bodyToFlux(Bucket.class));
}
public Flux<Bucket> getDataByWebClient() {
return webClient
.get()
.uri("/getAll")
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
System.out.println("4xx eror");
return Mono.error(new RuntimeException("4xx"));
})
.onStatus(HttpStatus::is5xxServerError, response -> {
System.out.println("5xx eror");
return Mono.error(new RuntimeException("5xx"));
})
.onStatus(HttpStatus::isError, clientResponse -> {
System.out.println("eror");
return Mono.error(new MyCustomServerException());
})
.bodyToFlux(Bucket.class);
}
}
Why is this not working? Can anyone tell me?
I want the browser to display the message "Server error" from my class with an error.
Thanks!

In the Spring3,How to call a another server's controller in my controller

I have 3 servers,serverA,serverB,serverC,Now in the serverC,some request from serverB is by processed,and then,I don't know what is the result(response),if it's resultA,I want give the resultA to the serverA as a request,else give the serverB.
so what I can do something in the serverC's controller,or there is something wrong in the desgin.
Please tell me what I should to do,Thanks.
This is my code.
serverA
#RestController
public class ControllerA {
#RequestMapping(value = "/methodA", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<String> methodA(#RequestBody String something) {
// some process
return null;
}
serverB
#RestController
public class ControllerB {
#RequestMapping(value = "/methodB", consumes =MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> methodB(#RequestBody String something) {
// some process
return null;
}
serverC
#RestController
public class ControllerC {
public ResponseEntity<String> methodC(#RequestBody String someReq) {
if (checkPam(someReq)) {
**// I want to call the ControllerA in serverA.**
}else {
**// I want to call the ControllerB in serverB.**
}
return null;
}
You can simply Use RestTemplate:
#RestController
public class ControllerC {
public ResponseEntity<String> methodC(#RequestBody String someReq) {
RestTemplate restTemplate = new RestTemplate();
if (checkPam(someReq)) {
String fooResourceUrl
= "http://path-to-server-a/path-to-service-a";
ResponseEntity<String> response
= restTemplate.getForEntity(fooResourceUrl , String.class);
}else {
String fooResourceUrl
= "http://path-to-server-b/path-to-service-b";
ResponseEntity<String> response
= restTemplate.getForEntity(fooResourceUrl , String.class);
}
return null;
}
As you can see, I instantiate RestTemplate object by new operator, you can also declare RestTemplate bean in your context and then autowire it in your controller class.

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