I am using a Micronaut layer between a REST service and a consumer app.
|REST service|->|Micronaut client / controller|->|consumer app|.
When the REST service is returning an error, the controller should propagate the error code.
When the REST service is offline, the controller should return some kind of 500 error code.
However, right now it's returning an empty body with a 200 error code in both cases.
For the example here is my controller:
#Controller("/api/v1")
public class MyController {
private final ClientNetworkList clientNetworkList;
public MyController(
ClientNetworkList clientNetworkList,
){
this.clientNetworkList = clientNetworkList;
}
#Get(uri = "/networkList", produces = MediaType.APPLICATION_JSON_STREAM)
Flowable<NetworkListPackage> packagesNetworkList() {
return clientNetworkList.fetchPackages();
}
}
And the client:
#Client(FabricConfiguration.FABRIC_API_URL)
public interface ClientNetworkList{
#Get("/auth/networklist")
Flowable<NetworkListPackage> fetchPackages();
}
How can I propagate or throw the correct body and error code?
How should I use the #Error annotation, should it be implemented in the controller or in a filter?
Micronaut has a Circuit Breaker Support for such things.
I would recommend to use the #Fallback Annotation and throw an Exception which will produce the 500 Error Code. See 7.3.7 Client Fallback in Micronaut Documentation.
Related
I am a beginner to the spring webflux. We are currently migrating our application to Spring Webflux. No I have stuck with a problem. The following is my scenario.
The main service class is calling the following service classes for data
StudentService - return Mono<Student>
StaffService - return Mono<Staff>
Here I have a wrapper class StudentWithMentor to store the result from these service classes.
public class StudentWithMentor {
private Student student;
private Staff mentor;
}
Now in controller I am calling the above 2 services and map it into 'StudentWithMentor' in the following way
Mono<StudentWithMentor> studentWithMentorMono = Mono.just(new StudentWithMentor());
return studentWithMentorMono.map(s->{
studentService.getStudentById(id)
.doOnSuccess(s::setStudent)
.doOnSuccess(st->staffService.getStaffByGrade(st.getGrade()));
return s;
});
But when I call this endpoint I am getting the following result in postman
{
"student": null,
"mentor": null
}
Note: I am getting result from the underlying services when I debugg. But the call is returning before it process.
How can I achieve this in a complete non-blocking way.
Appreciates any help.
The easiest way will be to to use a zipWith operator to merge the results into StudentWithMentor object.
See the code below:
Mono<StudentWithMentor> studentWithMentorMono = studentService.getStudentById(id)
.zipWhen(student -> staffService.getStaffByGrade(student.getGrade()), StudentWithMentor::new);
I receive the following error message:
Error resolving template [catalog/getCatalogItemFromCatalog/catalogItemId/3916677], template might not exist or might not be accessible by any of the configured Template Resolvers
I am trying to reach my service and the method using this url:
http://192.168.99.100:31003/catalog/getCatalogItemFromCatalog/catalogItemId/3916677
Controller:
#Controller
#RequestMapping("catalog")
public class CatalogController {
#GetMapping("/getCatalogItemFromCatalog/catalogItemId/{catalogItemId}")
public CatalogItem getCatalogItemFromCatalog(#PathVariable Integer catalogItemId){
List<Catalog> catalogs = getAllCatalogs();
Optional<CatalogItem> optionalCatalogItem = Optional.empty();
for(Catalog catalog : catalogs){
optionalCatalogItem = catalog.getCatalogItems().stream().filter(it -> it.getCatalogItemId().equals(catalogItemId)).findFirst();
}
return optionalCatalogItem.orElse(null);
}
#GetMapping("/system/ipaddr")
public String getIpAddr() {
List<String> response;
response = runSystemCommandAndGetResponse(IP_ADDR);
return new Gson().toJson(response);
}
}
When I curl
http://192.168.99.100:31003/catalog/system/ipaddr
I have no issues.
I am testing for hours now and nothing seems to work, I have no idea why its failing tho.
you have #Controller on your class which means spring will try to resolve the return type of all your methods inside the controller using all the available templateResolvers.
by using #ResponseBody spring will wrap the return type inside the response (after converting it) directly then returns it to the client, it's similar to using #RestController instead #Controller
When i send a POST request using netflix client , the json properties are blank when it hits the service consumer.
Below is my interface
#FeignClient(name = "NLPService", configuration = FooConfiguration.class )
public interface NLPServiceConsumer extends TempInterface {
}
public interface TempInterface {
#RequestMapping("/greeting")
String greeting();
#RequestMapping(method = RequestMethod.POST,value="/nlp",
consumes="application/json",produces="application/json")
NLPResponse identifyTags(NLPInputToBeTransformed nlpInputToBeTransformed);
#RequestMapping(method = RequestMethod.GET,value="/nlpGetMethod",
produces="application/json")
NLPResponse identifyTagsTest();
}
Method identifyTagsTest works and I am able to successfully get the response .
This method is a GET method with no input
When I try a POST method , passing a object as parameter , at the end point service implementation , the object attributes are null .
Has anybody faced such issue ? Is there any mistake in my configuration ?
The problem was not at the feign client. It was at the service implementation
Spent almost a day on this issue .
The RestController also has to specify #RequestBody ( apart from the shared interface )
can #FeignClient extend - and #RestController implement - a common, fully-annotated Interface?
I am creating a REST service using jersey 2.0. I am extending WebApplicationException
Method raising a particular exception
if(json.equals("") || json.equals(" ")) {
throw new ArgumentException("bad post data");
}
public class ArgumentException extends RestException {
.....
public ArgumentException(String message) {
super(Status.BAD_REQUEST,message);
}
}
public class RestException extends WebApplicationException {
...........
public RestException(Status status, String message) {
super(Response.status(status)
.entity(message)
.type("text/plain")
.build());
/*
super(Response.status(status)
.entity(new ErrorBean(status.getStatusCode(),message))
.type(MediaType.APPLICATION_JSON)
.build()); */
}
ErrorBean is a POJO
The method that returns error as plain string inside RestException works (right http code 400 and message). However when I try to pass the ErrorBean POJO and use MediaType.APPLICATION_JSON in response I get an error saying "Headers have already been sent" with http error code 500 (so some internal problem with plumbing) and empty response.
I have also looked at this question Returning JSON or XML for Exceptions in Jersey
How can I return the exception with code and message as a JSON like
{"code" : 400, "message" : .... }
Update
I have received answer on SO as well as jersey users mailing list. steps are
A non AJXB POJO does not need any annotations
Register JacksonFeature in your application
ResourceConfig rc = new ResourceConfig().packages("test").register(JacksonFeature.class);
You need to register JacksonFeature in your Application/ResourceConfig, i.e.:
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.jackson")
.register(JacksonFeature.class)
// No need to register this provider if no special configuration is required.
.register(MyObjectMapperProvider.class);
Take a look at the documentation for Jackson support in Jersey and also at the example.
At client side:
factory.find(proxyId).fire(new Receiver<P>()
{
#Override
public void onSuccess( P response )
{
proxy = response;
...
}
#Override
public void onFailure( com.google.web.bindery.requestfactory.shared.ServerFailure error )
{
Window.alert( error.getMessage() );
}
}
at server side i use an Locator like below:
public class myLocator extends Locator<T, String>
{
#Injector LocatorHook hook;
#Override
public T find( Class<? extends T> clazz, String id )
{
T result = ...;
hook.run( result );
return result;
}
....
}
The hook.run() method may throwRunTimeException("validation exception") there, i expect to catch the
exception at client side in onFailure(), however, i did catch the exception, but the message is "Internal Server Error",
not the exception i thrown in hook.run():"validation exception".
Any ideas to let client catch the exception i throw at server side?
Updation:
As Thomas said it's weird that validating objects that come fresh from data store, but i encounter a
situation that i don't know how to use service method:
At client i get EntityProxyId object, through the factory.find( proxyId ).fire(...) i can get the entity
from datastore, but the entity may not suitable for the user to access, in this situation i need to check it at server side, but i can't find a suitable place to do the
validation, Any ideas about this?
RequestFactory doesn't expect exceptions to be thrown by locators. Exceptions should only be thrown by service methods, and will be directed to the appropriate Receiver on the client-side (the one attached to the service method that threw).
Outside service methods, the only exceptions that gets routed to the client are ReportableExceptions, that can only be thrown from a ServiceLocatorDecorator's report() methods. That means you could hook your own ServiceLocatorDecorator that catches exceptions from your locators and report()s them.
That said, validating objects that come fresh from your data store seems weird. You might want to provide a ServiceLocatorDecorator that overrides validate() (that'll validate the objects after the changes coming from the client have been applied). The errors will go back to the client in the Receiver's onConstraintViolations, and the RequestContext will be unfrozen so you can further edit your proxies and fire() again.