Send Async message to microservice - spring

I have a BE service A which is sending Rest JSON message to microservice B using Feign client:
#FeignClient(name = "mail-service")
#LoadBalancerClient(name = "mail-service", configuration = LoadBalancerConfiguration.class)
public interface EmailClient {
#RequestMapping(method = RequestMethod.POST, value = "/engine/emails/register")
void setUserRegistration(CreateUserDTO createUserDTO);
}
Endpoint:
#RestController
#RequestMapping("/emails")
public class EmailController {
#RequestMapping(method = RequestMethod.POST, value = "/register", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> register(#Valid #RequestBody CreateUserDTO createUserDTO) {
emailRestService.processCreateUserMessage(createUserDTO);
// Implementation of service to send mail to AWS SES
return new ResponseEntity<>(HttpStatus.OK);
}
}
Rest Endpoint is sending mail to AWS Ses mail or other mail provider.
The issue is that the fist call from Feign can take 5 seconds and more. I need to make it Async in order FE client not to wait for the mail to be send.
How I can make the Rest call made from Feign Async to there is no wait time for the http response OK to be expected? Is there some better solution to implement this?

AFAIK, Feign does not allow for non-blocking IO, it is a work in progress.
But you can implement your EmailRestService async. Consider the following code (I do no know if processCreateUserMessage is responsible for sending the email as well, but the solution proposed should be extensible to that functionally if necessary):
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
//...
#Service
public class EmailRestServiceImpl implements EmailRestService {
//...
#Async
public void processCreateUserMessage(CreateUserDTO createUserDTO) {
// Implementation of service to send mail to AWS SES
// ...
}
}
Please, note the #Async annotation definition.
To enable Spring asynchronous processing, you need to define the #EnableAsync annotation, in your main configuration or in a specific one:
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
#Configuration
#EnableAsync
public class AsyncConfiguration {
}
There is no need to change your Controller, although you can return a more convenient HTTP status code if you prefer to:
#RestController
#RequestMapping("/emails")
public class EmailController {
#RequestMapping(method = RequestMethod.POST, value = "/register", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> register(#Valid #RequestBody CreateUserDTO createUserDTO) {
// Will be executed asynchronously and return immediately
emailRestService.processCreateUserMessage(createUserDTO);
return new ResponseEntity<>(HttpStatus.ACCEPTED);
}
}

Related

How do I set the HttpStatus code when using #ResponseBody?

In a SpringBoot Controller class, my APIs usually return a ResponseEntity with a body and a status code. But I can apparently dispense with the ResponseEntity by annotating my controller method with #ResponseBody, like this:
#Controller
public class DemoController
{
#Autowired
StudentService studentService;
#GetMapping("/student")
#ResponseBody
Student getStudent(#RequestParam id) {
return studentService.getStudent(id);
}
}
If my service throws an exception, I can return a custom HTTP status by throwing a ResponseStatusException, but it's not clear how to specify the HTTP status for a valid response. How would I specify this? Or how does it decide what to use?

How to make sure that the content of the request's parameter is safe?

This is my first time writing something in Spring. I add to the code already in the repo a small service that accepts a request parameter from the client and passes it to a third-party API.
The service contains two classes, not counting the interface:
#RestController
#RequestMapping("/store/search")
#CrossOrigin("*")
public class APToid {
private AptoidSearch aptoidSearch;
#GetMapping
public ResponseEntity<String> searchInAptoidStore(#RequestParam("query") String query) {
return aptoidSearch.aptoidSearch(query);
}
}
and
#Service
public class AptoidSearchImpl implements AptoidSearch {
#Autowired
private APToidUriConfiguration configuration;
#Override
public ResponseEntity<String> aptoidSearch(String query) {
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForEntity(configuration.getUri() + query, String.class);
}
}
The query string, as you can see, is taken directly from the client. What are some ways to make sure the client isn't giving me some dangerous shit, like exploit, etc?

Wrong CORS configuration in a SpringBoot project

I'm developing a CRUD app that will serve REST in the SpringBoot part (it will have an Angular part too, consuming JSON). The SpringBoot part serves gracefully JSON (queries against a MySQL database) but when I run the part that tries to delete a record I get a 405 error:
"There was an unexpected error (type=Method Not Allowed, status=405)."
This is the code that fails (it's calling a #Service)
#RequestMapping(value = "/avisos/delete/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Void> borraAviso(#RequestParam("id") Long id) {
boolean isRemoved;
isRemoved = avisoService.borraAviso(id);
if (!isRemoved) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
else
return new ResponseEntity<>(HttpStatus.OK);
}
This is the CORS configuration file:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class CorsConfiguration implements WebMvcConfigurer
{
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
//.allowedMethods("GET", "POST");
.allowedMethods("");
}
}
The program runs in a Linux Mint box but I have tested that in a W8 box too and I get the same error.
(I'm using Spring Tool Suite 4, Version: 4.8.0.RELEASE, and Maven).
The declaration of the method has one issue
#RequestMapping(value = "/avisos/delete/{id}", method =
RequestMethod.DELETE) public ResponseEntity
borraAviso(#RequestParam("id") Long id) {
Here the id is a PathVariable. So the correct declaration would be
#RequestMapping(value = "/avisos/delete/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Void> borraAviso(#PathVariable("id") Long id) {
By default, GET, HEAD, POST method are allowed for CORS if not overridden.
If you want to allow DELETE method, then the following config should work.
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods(HttpMethod.GET.name(),
HttpMethod.HEAD.name(),
HttpMethod.POST.name(),
HttpMethod.DELETE.name()
);

Spring-boot REST API print caller Address

I am trying to print the IP address of the requester of my web service.
I am a beginner with spring-boot, and I am not sure which class to import or variable to use to print the caller IP and port number.
This is my controller class :
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;//added
import org.springframework.web.bind.annotation.RequestParam;//added
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.sql.*;
//Controller
#RestController
public class MyController {
#GetMapping(path = "/print-caller-address")
public String CallerAddress() {
return "Caller IP or Port Number";
}
}
I am using spring-boot-starter-web and spring-boot-starter-data-jpa as dependencies.
Many thanks.
Try this:
#RestController
public class MyController {
#GetMapping(path = "/print-caller-address")
public String getCallerAddress(HttpServletRequest request) {
return request.getRemoteAddr();
}
}
https://docs.oracle.com/javaee/6/api/javax/servlet/ServletRequest.html#getRemoteAddr()
Returns the Internet Protocol (IP) address of the client or last proxy
that sent the request.
If the request has gone through a proxy before hitting your REST server then you would need to look at the headers. The proxy will typically set a header idientifyign the originating IP, address as detailed below:
https://en.wikipedia.org/wiki/X-Forwarded-For
so you can use request.getHeader("X-Forwarded-For") to get the originating IP address. To catch all scenarios:
#RestController
public class MyController {
#GetMapping(path = "/print-caller-address")
public String getCallerAddress(HttpServletRequest request) {
if(request.getHeader("X-Forwarded-For") != null){
return request.getHeader("X-Forwarded-For")
}else{
return request.getRemoteAddr();
}
}
}
The solution is
#GetMapping(path = "/print-caller-address")
public String CallerAddress(HttpServletRequest request) {
return request.getRemoteAddr();
}
Add HttpServletRequest request to your method definition and then use the Servlet API
Spring Documentation here said in
15.3.2.3 Supported handler method arguments and return types
Handler methods that are annotated with #RequestMapping can have very flexible signatures.
Most of them can be used in arbitrary order (see below for more details).
Request or response objects (Servlet API). Choose any specific request or response type,
for example ServletRequest or HttpServletRequest

Apache Camel Spring Javaconfig Unit Test No consumers available on endpoint

I have the following route configuration:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:in").to("direct:out");
}
}
When I try to test it:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpoints
public class MyRouteTest {
#EndpointInject(uri = "mock:direct:out")
private MockEndpoint mockEndpoint;
#Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;
#Configuration
public static class TestConfig extends SingleRouteCamelConfiguration {
#Bean
#Override
public RouteBuilder route() {
return new MyRoute();
}
}
#Test
public void testRoute() throws Exception {
mockEndpoint.expectedBodiesReceived("Test Message");
producerTemplate.sendBody("Test Message");
mockEndpoint.assertIsSatisfied();
}
}
I get this exception:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: Endpoint[direct://out].
Exchange[Message: Test Message]
It looks like the Mock is not picking up the message from the endpoint.
What am I doing wrong?
The problem is that mock endpoints just intercept the message before delegating to the actual endpoint. Quoted from the docs:
Important: The endpoints are still in action. What happens differently
is that a Mock endpoint is injected and receives the message first and
then delegates the message to the target endpoint. You can view this
as a kind of intercept and delegate or endpoint listener.
The solution to your problem is to tell certain endpoints (the ones that expect a consumer in your case) not to delegate to the actual endpoint. This can easily be done using #MockEndpointsAndSkip instead of #MockEndpoints:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpointsAndSkip("direct:out") // <-- turns unit test from red to green ;)
public class MyRouteTest {
// ....
}
This issue because, in your route configuration, there is no route with "direct:out" consumer endpoint.
add a line like some thing below,
from("direct:out").("Anything you want to log");
So that direct:out will consume the exchange and In your test, mock will be able check the received text without any issues. Hope this helps !!

Resources