I have an issue with an endpoint that is not behaving as I would expect. Basically, it follows the flow the way I would expect and it hits the return statement BEFORE it fires the callback methods. So, all seems good.
However, when I test this in Postman, the method still hangs. It blocks and does not return a response until the callback onSuccess is executed. Is this how this should behave or am I missing something?
#RequestMapping(value = "/async", method = RequestMethod.POST)
#ResponseBody
public DeferredResult<String> createAsync(#RequestBody Input input) throws CombineException {
AsyncRestTemplate restTemplate = new AsyncRestTemplate();
DeferredResult<String> result = new DeferredResult();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<?> entity = new HttpEntity<>(input , headers);
ListenableFuture<ResponseEntity<Input>> future = restTemplate.exchange("http://localhost:8080/myLongRunningEndpointIDontWantToWaitFor", HttpMethod.POST, entity, Input.class);
future.addCallback(new ListenableFutureCallback<ResponseEntity<Input>>() {
#Override
public void onSuccess(ResponseEntity<Input> response) {
System.out.println("Success");
result.setResult(response.getBody().toString());
}
#Override
public void onFailure(Throwable t) {
System.out.println("FAILED");
result.setErrorResult(t.getMessage());
}
});
System.out.println("RETURNING...");
return result;
}
Related
I'm sending a response to another web service to create an user. If the user already exists it sends back the 409 response. I'm using RestTemplate like so:
#PostMapping("/todos/{toDoNoteId}/users")
public ResponseEntity <String> postUser(#RequestBody User user, #PathVariable int toDoNoteId, UriComponentsBuilder builder)throws HttpMessageNotReadableException, ParseException{
RestTemplate restTemplate = new RestTemplate();
final String uri = "http://friend:5000/users";
try {
ResponseEntity<String> result = restTemplate.postForEntity(uri, user, String.class);
return result;
}
catch (HttpClientErrorException ex) {
return ResponseEntity.status(ex.getRawStatusCode()).headers(ex.getResponseHeaders())
.body(ex.getResponseBodyAsString());
}
}
While catching an exception somewhat works (in the catch block i can access the status code and body), is there a way to access it without exceptions something similar like this:
#PostMapping("/todos/{toDoNoteId}/users")
public ResponseEntity <String> postUser(#RequestBody User user, #PathVariable int toDoNoteId, UriComponentsBuilder builder)throws HttpMessageNotReadableException, ParseException{
RestTemplate restTemplate = new RestTemplate();
final String uri = "http://friend:5000/users";
ResponseEntity<String> result = restTemplate.postForEntity(uri, user, String.class);
if(result.getStatusCode()=="409"){
// do something
}
else{
// do something else
}
return result;
}
Have you been check the ExceptionHandler? When exception throws, ExceptionHandler handles it.
For example:
#ControllerAdvice()
public class CustomExceptionHandler {
private static final Logger logger = LogManager.getLogger("CustomExceptionHandler");
#ExceptionHandler(YourException.class)
public ResponseEntity handleYourException(HttpServletRequest request, YourException ex) {
return ResponseEntity.ok("");
}
#ExceptionHandler(Exception.class)
public ResponseEntity handleException(HttpServletRequest request, Exception ex) {
logExp("Exception", request, ex);
//return new ResponseEntity<>();
return null;
}
}
You can create your own custom resttemplate and define exception handler. Here is a code snippet.
#Configuration
public class CustomRestTemplate extends RestTemplate {
#Autowired
private CustomErrorHandler customErrorHandler;
#PostConstruct
public void init() {
this.setErrorHandler(customErrorHandler);
}
}
#Component
public class CustomErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
if(response.getStatusCode() != "409"){
return true;
}else {
return false;
}
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
String responseBody = response.getBody();//Pls read from InputStream and create write into String
JSONObject jsonObj = new JSONObject(result);
JSONArray jsonArray = new JSONArray();
jsonObj.put("status", response.getStatusCode());
jsonObj.put("body", responseBody );
jsonArray.put(jsonObj);
responseString = jsonArray.get(0).toString();
throw new MyException(responseString );
}
}
class MyException throw RuntimeException {
public MyException (String message) {
super(message);
}
}
So, your class will changed to
#PostMapping("/todos/{toDoNoteId}/users")
public ResponseEntity <String> postUser(#RequestBody User user, #PathVariable int toDoNoteId, UriComponentsBuilder builder)throws HttpMessageNotReadableException, ParseException{
CustomRestTemplate restTemplate = new CustomRestTemplate ();
final String uri = "http://friend:5000/users";
ResponseEntity<String> result = restTemplate.postForEntity(uri, user, String.class);
return result
}
I'm performing a Rest request in one of my controllers and I'd like to redirect to my error view if the request went wrong (404, 503 ...)
My controller calls this function :
public String functionTest(){
String date, res;
String url = "myRestUrl/{param}";
Map<String, String> uriParams = new HashMap<>();
uriParams.put("param", "param");
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
HttpEntity<String> request = new HttpEntity<>(createHeaders());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestErrorHandler());
ResponseEntity<String> response = restTemplate.exchange(builder.buildAndExpand(uriParams).toUri(), HttpMethod.GET, request, String.class);
res= response.getBody().getMyResult();
return res;
}
And here is my Rest error handler :
public class RestErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
//Here I need to use a ModelAndView to redirect to error view but I'm not anymore in my controller
}
}
I guess I'm doing it wrong, do you have any solutions ?
Here is a rather simplistic way to achieve what you need. If you would like more control/flexibility, refer to this Spring blog article.
In your (client) RestErrorHandler:
public class RestErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response)
throws IOException {
// Do some stuff here ...
// This could be your own exception, for example.
throw new IOException();
}
}
Then in your controller (or refer to the article above if you want other options):
// Your requestMappings here ...
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
ModelAndView mav = new ModelAndView();
// Do stuff and redirect to your error view.
return mav;
}
EDIT: Another solution would be to catch Spring's RestClientException, which is a RuntimeException thrown by RestTemplate when it encounters an error:
try {
restTemplate.exchange(...);
} catch (RestClientException ex) {
// Do stuff here ...
}
I'm working on a Spring project.
I'm currently doing this.
public ResponseEntity<?> create(#RequestBody final Some entity) {
// persist the entity here
final URI location = uriComponentsBuilder.path("{id}").buildAndExpand(entity.getId()).toUri();
return ResponseEntity.created(location).build();
}
And I found the #ResponseStatus.
#ResponseStatus(HttpStatus.CREATED)
public void create(#RequestBody #NotNull final BaseType entity) {
// persist the entity here
// Location header to where?
}
Is there any way to send Location header with this way?
You can return response entity like below:
return new ResponseEntity<>(location, HttpStatus.CREATED);
or
HttpHeaders headers = new HttpHeaders();
headers.add(location);
return new ResponseEntity<>(headers, HttpStatus.CREATED);
Try this. It returns your preferred header and status, without body.
#ResponseStatus(HttpStatus.I_AM_A_TEAPOT)
#RequestMapping("/teapot")
public HttpHeaders dummyMethod() {
HttpHeaders h = new HttpHeaders();
h.add("MyHeader", "MyValue");
return h;
}
I'm trying to test a basic blocking and non-blocking API controller that stream OkHttp response like following:
#SpringBootApplication
public class HttpProxyServiceApplication {
public static void main(String[] args) {
SpringApplication.run(HttpProxyServiceApplication.class, args);
}
#Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient();
}
#RestController
#RequestMapping(path = "/test")
public static class TestController {
private static final String URL = ...;
#Autowired
private OkHttpClient client;
#RequestMapping(method = RequestMethod.GET, path = "blocking")
public void blocking(HttpServletResponse httpServletResponse) throws IOException {
Request request = new Request.Builder()
.url(URL)
.build();
try (ResponseBody body = client.newCall(request).execute().body()) {
StreamUtils.copy(body.byteStream(), httpServletResponse.getOutputStream());
}
}
#RequestMapping(method = RequestMethod.GET, path = "non-blocking")
public DeferredResult<ResponseEntity<?>> nonBlocking() {
Request request = new Request.Builder()
.url(URL)
.build();
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Request request, IOException e) {
ResponseEntity<Void> responseEntity =
new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE);
deferredResult.setResult(responseEntity);
}
#Override
public void onResponse(Response response) throws IOException {
ResponseEntity<InputStreamResource> responseEntity =
new ResponseEntity<>(new InputStreamResource(response.body().byteStream()),
HttpStatus.ACCEPTED);
deferredResult.setResult(responseEntity);
}
});
return deferredResult;
}
}
}
Important thing: I want to stream result ! I don't want to save whole result in memory. That's why for non-blocking I created a ResponseEntity<InputStreamResource>.
However using basic JMeter script that crawls on both API, the non-blocking is really slower than the blocking one
500 threads
Blocking result:
Non-blocking result:
There is any reason?
Update 1:
New test
#RequestMapping(method = RequestMethod.GET, path = "non-blocking-2")
public DeferredResult<InputStreamResource> nonBlockingBlocking() throws IOException {
Request request = new Request.Builder()
.url(URL)
.build();
DeferredResult<InputStreamResource> deferredResult = new DeferredResult<>();
deferredResult.setResult(new InputStreamResource(client.newCall(request).execute().body().byteStream()));
return deferredResult;
}
As similar result as full blocking. So I don't think is performance issue directly come from DeferredResult.
This is my rest controller (server):
#RestController
public class RemoteController {
#RequestMapping(value="/test", method=RequestMethod.GET)
public Return serverTest(HttpServletRequest req, SearchFilter search) throws Exception{
//...
return new OutputTest();
}
}
I want to write the corresponding client for this GET controller with SearchFilter object as input.
public void clientTest(){
SearchFilter input=new SearchFilter();
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity = input;// how to store SearchFilter input ??????
ResponseEntity<OutputTest> response=restTemplate.exchange("http://localhost:8080/test", HttpMethod.GET, entity, OutputTest.class);
OutputTest out=response.getBody();
}
How can I send a single object to restTemplate?
You should tell Spring how to bind the request parameters to SearchFilter. There are multiple approachs to achieve that, The simplest solution is to use ModelAttribute annotation:
#RequestMapping(value="/test", method=RequestMethod.GET)
public Return serverTest(HttpServletRequest req, #ModelAttribute SearchFilter search) throws Exception{
//...
return new OutputTest();
}
Supposing your SearchFilter looks like this:
public class SearchFilter {
private String query;
// other filters and getters and setters
}
If you fire a request to /test?query=something, the SearchFilter will be populated with the sent query parameter. In order to send this request with RestTemplate:
RestTemplate template = new RestTemplate();
// prepare headers
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
// request without body, just headers
HttpEntity<Object> request = new HttpEntity<>(headers);
ResponseEntity<OutputTest> response = template.exchange("http://localhost:8080/test?query=something",
HttpMethod.GET,
request,
OutputTest.class);
The other approach i can think of, is to implement a HandlerMethodArgumentResolver for resolving SearchFilter arguments. Also, you can break the SearchFilter apart and use multiple RequestParams.