Feign Client Error Handling - spring-boot

I am using Feign Client,
I have a Location service. So I created a client for my LocationService using FeignClient.
#FeignClient(url="http://localhost:9003/location/v1", name="location-service")
public interface LocationControllerVOneClient {
#RequestMapping(value = "/getMultipleLocalities", method = RequestMethod.POST)
public Response<Map<Integer, Locality>> getMultipleLocalities(List<Integer> localityIds);
#RequestMapping(value = "/getMultipleCities", method = RequestMethod.POST)
public Response<Map<Integer, City>> getMultipleCities(List<Integer> cityIds);
#RequestMapping(value = "/getMultipleStates", method = RequestMethod.POST)
public Response<Map<Integer, State>> getMultipleStates(List<Integer> stateIds);
#RequestMapping(value = "/getMultipleCitiesName", method = RequestMethod.POST)
public Response<Map<Integer, String>> getMultipleCitiesName(MultiValueMap<String, String> formParams);
#RequestMapping(value = "/getMultipleStatesName", method = RequestMethod.POST)
public Response<Map<Integer, String>> getMultipleStatesName(MultiValueMap<String, String> formParams);
#RequestMapping(value = "/getMultipleLocalitiesName", method = RequestMethod.POST)
public Response<Map<Integer, String>> getMultipleLocalitiesName(MultiValueMap<String, String> formParams);
}
Now other services might call this LocationService via LocationClient.
I want to do exception handling for this Feign Client(LocationClient) at a common place(i.e. I just donot want each caller to do this. This should be part of LocationClient). Exception Could be connection refused(if LocationService is down), timeout etc.

You could use a feign ErrorDecoder for exception handling. Below is the url for your reference.
https://github.com/OpenFeign/feign/wiki/Custom-error-handling
Example :
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyBadRequestException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
To get this ErrorDecoder you need create a bean for it as below :
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}

You can define a fallback client that is called when an exception like timeout or connection refused comes up:
#FeignClient(url="http://localhost:9003/location/v1", name="location-service", fallback=LocationFallbackClient.class)
public interface LocationControllerVOneClient {
...
}
LocationFallbackClient must implement LocationControllerVOneClient.

Related

Get return value in custom annotation spring aop

I have write a simple custom annotation to set HttpHeaders to ResponseEntity because of duplicating the code every where .
Annotation Interface and Class.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface JsonHeader {}
#Component
#Aspect
public class JsonHeaderAspect {
private final Log logger = LogFactory.getLog(getClass());
#Around(value = "#annotation(JsonHeader)")
public Object aroundServiceResponse(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
return proceedingJoinPoint.proceed(new Object[] {headers});
}
}
RestController Class
#RestController
#RequestMapping(path = "/login")
public class LoginRestController {
private final Log logger = LogFactory.getLog(getClass());
#Autowired
LoginServiceImpl loginService;
#JsonHeader
#RequestMapping(value = "/user",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResponseBean> postCheckUser(#RequestBody LoginBean loginBean) {
ResponseBean responseBean = loginService.checkUser(loginBean);
return new ResponseEntity<ResponseBean>(responseBean,headers, HttpStatus.OK);
}
}
Now I want to get the return HttpHeaders value annotaion to rest controller class.
Is any one can describe why it happen and how to fix this issue it will be great helpful. Thanks in advance
You can do this simply by modifying your advice like this. You don't need to do anything in the controller.
#Around(value = "#annotation(requestMapping)")
public Object aroundServiceResponse(ProceedingJoinPoint proceedingJoinPoint,RequestMapping requestMapping) throws Throwable {
String[] consumes = requestMapping.consumes();
consumes[consumes.length] = MediaType.APPLICATION_JSON_VALUE;
String[] produces = requestMapping.produces();
produces[produces.length] = MediaType.APPLICATION_JSON_VALUE;
return proceedingJoinPoint.proceed();
}

Can not attach body to my POST request using Spring MockMvc

I'm trying to test my rest controller. No issues with GETs, but when I try to test a POST method I'm unable to attach the body.
private static final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
private ObjectMapper jsonMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
#Test
public void test1() throws Exception {
//...Create DTO
//...Create same pojo but as entity
when(serviceMock.addEntity(e)).thenReturn(e);
mvc.perform(post("/uri")
.contentType(contentType)
.content(jsonMapper.writeValueAsString(dto))
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(content().contentType(contentType)); //fails because there is no content returned
}
This is the request output:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /uri
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
There is no body. Why? I have printed jsonMapper.writeValueAsString(dto) and is not null.
edit:
Adding controller code:
#RestController
#RequestMapping("/companies")
public class CompanyController {
#Autowired
private CompanyService service;
#Autowired
private CompanyMapper mapper;
#RequestMapping(method=RequestMethod.GET)
public List<CompanyDTO> getCompanies() {
List<Company> result = service.getCompanies();
return mapper.toDtoL(result);
}
#RequestMapping(method=RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public CompanyDTO createCompany(#RequestBody #Valid CompanyDTO input) {
Company inputE = mapper.toEntity(input);
Company result = service.addCompany(inputE);
return mapper.toDto(result);
}
Solved.
The mock call should use any instead of a concrete object: when(serviceMock.addCompany(any(Company.class))).thenReturn(e);
I needed to override the equals method of the entity class to pass this statement: verify(serviceMock, times(1)).addCompany(e);

Rest template giving null body and status 302

I am trying to consume a rest call in my mvc controller, however every time I do it returns a null body with http status as 302.Also I am using spring boot with spring security to get https.
I've followed code samples from here: http://websystique.com/springmvc/spring-mvc-4-restful-web-services-crud-example-resttemplate/
and Get list of JSON objects with Spring RestTemplate however none of these work
Can someone please point me in the right direction
Thank you,
REST
#RequestMapping(value = "/api/*")
#RestController
public class PostApiController {
static final Logger logger = LogManager.getLogger(PostApiController.class.getName());
private final PostService postService;
#Inject
public PostApiController(final PostService postService) {
this.postService = postService;
}
//-------------------Retrieve All Posts--------------------------------------------------------
#RequestMapping(value = "post", method = RequestMethod.GET)
public ResponseEntity<List<Post>> getAllPosts() {
List<Post> posts = postService.findAllPosts();
if(posts.isEmpty()){
return new ResponseEntity<List<Post>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
}
return new ResponseEntity<List<Post>>(posts, HttpStatus.OK);
}
}
Controller
#Controller
public class PostController {
static final Logger logger = LogManager.getLogger(PostController.class.getName());
public static final String REST_SERVICE_URI = "http://localhost:8080/api"; //"http://localhost:8080/api";
private final PostService postService;
#Inject
public PostController(final PostService postService) {
this.postService = postService;
}
#SuppressWarnings("unchecked")
#RequestMapping(value = "/getAll")
// public String create(#Valid Post post, BindingResult bindingResult, Model
// model) {
public ModelAndView getAll() {
// if (bindingResult.hasErrors()) {
// return "mvchome";
// }
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<Post>> responseEntity = restTemplate.exchange(REST_SERVICE_URI+"/post",HttpMethod.GET, null, new ParameterizedTypeReference<List<Post>>() {});
// ResponseEntity<Post[]> responseEntity = restTemplate.getForEntity(REST_SERVICE_URI+"/post", Post[].class);
List<Post> postsMap = responseEntity.getBody();
MediaType contentType = responseEntity.getHeaders().getContentType();
HttpStatus statusCode = responseEntity.getStatusCode();
// List<LinkedHashMap<String, Object>> postsMap = restTemplate.getForObject(REST_SERVICE_URI+"/post", List.class);
// String s= REST_SERVICE_URI+"/post";
// logger.info(s);
if(postsMap!=null){
for(Post map : postsMap){
logger.info("User : id="+map.getUid());
}
}else{
logger.info("No user exist----------");
}
//List<Post> postList = postService.findAllPosts();
ModelAndView mav = new ModelAndView("mvchome");
mav.addObject("postsList", postsMap);
Post newpost = new Post();
mav.addObject("post", newpost);
return mav;
}
}
***** to fix my issue I modified my code to just do a redirect on select url paths instead of "/*"
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat =
new TomcatEmbeddedServletContainerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
//used to be just collection.addPattern("/*"); now I changed it to specify which path I want it to redirect
collection.addPattern("/mvchome/*");
collection.addPattern("/home/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(createHttpConnector());
return tomcat;
}
The http status 302 is usually caused by wrong url setting.
First, make sure that public ResponseEntity<List<Post>> getAllPosts() {} method is called (just print List<Post> result inside it).
If it's called properly and you can get the return value inside public ModelAndView getAll() {}.
The problem should be the directing setting of the public ModelAndView getAll() {} method.
Check if you make something wrong in your web.xml or spring configuration. Pay attention to the configuration which redirects to views and the url mapping of your dispatcher servlet.
If public ResponseEntity<List<Post>> getAllPosts() {} is called but you can't get the return value, then it should be the issues of directing setting of the public ResponseEntity<List<Post>> getAllPosts() {} method.
Check your spring configuration and web.xml for that. The possible cause usually will be the misuse of wildcard in the configuration and web.xml, or just unnoticed wrong mapping.

Spring deferredResult performance issue with Okhttp

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.

Spring Framework TEST RESTful Web Service (Controller) Offline i.e. No Server, No Database

I have a very simple RESTful Controller that consumes and produces JSON. I need to test this controller offline i.e. no server running, no database running. And I am going nuts for not being able to find a solution. My intial test cases will include:
Test REST URIs i.e. GET, POST, PUT, DELETE - I must be able to Assert data returned against data sent.
Assert will test JSON data
I have the following URIs:
/pcusers - Returns all users
/pcusers/{id} - Return a specific user
/pcusers/create/{pcuser} - Add user to db
/pcusers/update/{pcuser} - Update user
/pcusers/delete/{id} - Delete User
NOTE: This is NOT a typical MVC application. I DO NOT have Views. I have a pure REST controller that spits out JSON and consumes data in JSON format.
If someone could guide me in the right direction would be really appreciated.
Just to be clear how my code looks like:
#Controller
#RequestMapping("/pcusers")
public class PcUserController {
protected static Logger logger = Logger.getLogger(PcUserController.class);
#Resource(name = "pcUserService")
private PcUserService pcUserService;
#RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
#ResponseBody
public List<PcUser> readAll() {
logger.debug("Delegating to service to return all PcUsers");
return pcUserService.readAll();
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET, consumes = "application/json", produces = "application/json")
#ResponseBody
public PcUser read(#PathVariable String id) {
logger.debug("Delegating to service to return PcUser " + id);
return pcUserService.read(id);
}
#RequestMapping(value = "/create/{pcUser}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
#ResponseBody
public boolean create(#PathVariable PcUser pcUser) {
logger.debug("Delegating to service to create new PcUser");
return pcUserService.create(pcUser);
}
#RequestMapping(value = "/update/{pcUser}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
#ResponseBody
public boolean update(#PathVariable PcUser pcUser) {
logger.debug("Delegating to service to update existing PcUser");
return pcUserService.update(pcUser);
}
#RequestMapping(value = "/delete/{id}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
#ResponseBody
public boolean delete(#PathVariable String id) {
logger.debug("Delegating to service to delete existing PcUser");
return pcUserService.delete(id);
}
}
UPDATE (2/5/2012):
After some research, I came across a Spring framework called spring-test-mvc. It looks very promising and I have managed to get a good start on this. But now I have a new problem. When I submit a GET request to "/pcusers/{id}", the control is passed to read method which is responsible for handling that mapping. Inside that method I have a pcUserService that does a read. Now, the problem is when I run this test, the pcUserService instance inside real controller is NULL; and therefore it ends up crashing as read cannot be called on a NULL object.
Here's PcUserControllerTest code:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:/applicationContextTest.xml")
public class PcUserControllerTest {
#Autowired
PcUserService pcUserService;
#Autowired
PcUserController pcUserController;
PcUser pcUser;
#Before
public void setUp() throws Exception {
pcUser = new PcUser("John", "Li", "Weasley", "john", "john", new DateTime());
pcUserService.create(pcUser);
}
public void tearDown() throws Exception {
pcUserService.delete(pcUser.getId());
}
#Test
public void shouldGetPcUser() throws Exception {
standaloneSetup(pcUserController)
.build()
.perform(get("/pcusers/" + pcUser.getId()).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Here is one suggestion that should give you some ideas. I assume that you are familiar with the SpringJUnit4ClassRunner and the #ContextConfiguration. Start by creating an test application context that contains PcUserController and a mocked PcUserService. In the example PcUserControllerTest class below, Jackson is used to convert JSON messages and Mockito is used for mocking.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(/* Insert test application context here */)
public class PcUserControllerTest {
MockHttpServletRequest requestMock;
MockHttpServletResponse responseMock;
AnnotationMethodHandlerAdapter handlerAdapter;
ObjectMapper mapper;
PcUser pcUser;
#Autowired
PcUserController pcUserController;
#Autowired
PcUserService pcUserServiceMock;
#Before
public void setUp() {
requestMock = new MockHttpServletRequest();
requestMock.setContentType(MediaType.APPLICATION_JSON_VALUE);
requestMock.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
responseMock = new MockHttpServletResponse();
handlerAdapter = new AnnotationMethodHandlerAdapter();
HttpMessageConverter[] messageConverters = {new MappingJacksonHttpMessageConverter()};
handlerAdapter.setMessageConverters(messageConverters);
mapper = new ObjectMapper();
pcUser = new PcUser(...);
reset(pcUserServiceMock);
}
}
Now, we have all the code needed to create the tests:
#Test
public void shouldGetUser() throws Exception {
requestMock.setMethod("GET");
requestMock.setRequestURI("/pcusers/1");
when(pcUserServiceMock.read(1)).thenReturn(pcUser);
handlerAdapter.handle(requestMock, responseMock, pcUserController);
assertThat(responseMock.getStatus(), is(HttpStatus.SC_OK));
PcUser actualPcUser = mapper.readValue(responseMock.getContentAsString(), PcUser.class);
assertThat(actualPcUser, is(pcUser));
}
#Test
public void shouldCreateUser() throws Exception {
requestMock.setMethod("POST");
requestMock.setRequestURI("/pcusers/create/1");
String jsonPcUser = mapper.writeValueAsString(pcUser);
requestMock.setContent(jsonPcUser.getBytes());
handlerAdapter.handle(requestMock, responseMock, pcUserController);
verify(pcUserServiceMock).create(pcUser);
}

Resources