Handling exceptions in Spring 5 Reactive kotlin - exception

I've been hitting the wall and haven't came up with any reasonable solution, so maybe someone will give it a try. I wrote simple service integrating with github, and having hard time to understand how should I work with exceptions in reactive word properly. Once I got expected 404 status error from Github I would like to throw my custom exception and present it to the client instead of valid response, I'm checking code statuses of response from github and the only thing I receive on my site is:
2018-06-26 21:45:08.286 WARN 8336 --- [ctor-http-nio-2]
.a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request
[GET http://localhost:8080/repositories/sh1nen/no-exist]: Response
status 404
Here is my simple method responsible for making requests and handling error codes appropriately.
fun findSpecificOwnerRepository(owner: String, repositoryName: String) = webClient
.get()
.uri("/repos/$owner/$repositoryName")
.retrieve()
.onStatus({ httpStatus -> HttpStatus.NOT_FOUND == httpStatus }, { Mono.error(RepositoryNotFoundException(reason = "Repository $repositoryName not found.")) })
.onStatus({ httpStatus -> HttpStatus.SERVICE_UNAVAILABLE == httpStatus }, { Mono.error(RepositoryNotFoundException(reason = "Service unavailable.")) })
.bodyToMono(GithubRepositoryResponse::class.java)
Here is my custom exception which basically represents no resources on my site to represent:
internal class RepositoryNotFoundException(
status: HttpStatus = HttpStatus.NOT_FOUND,
reason: String? = null,
throwable: Throwable? = null) : ResponseStatusException(status, reason, throwable)
And the endpoint itself which I'm hitting to get the response:
#GetMapping("{owner}/{repositoryName}")
fun findSpecificOwnerRepository(#PathVariable owner: String, #PathVariable repositoryName: String) = githubClient
.findSpecificOwnerRepository(owner, repositoryName)
I would like to get 404 with a message which is hardcoded. Do I need any special #ExceptionHandler in controller to handle my custom exception ?
Is there any chance of implementing situation when for example github is not able to keep up with requests I am serving and throw in that case also some exception? How could it be implemented?

I'm not sure if you are actually missing anything for point 1), as the exception you extend should naturally result in 404 to your clients, if I recall correctly.
About point 2, it all depends on how your source handles rate limiting. In the case of GitHub, it will return a 403 once you hit rate limits, but you can be extra careful and check the custom headers as well. See https://developer.github.com/v3/#rate-limiting
So the simplest way it would be implemented is with onStatus. Alternatively, you can inspect the whole response and act accordingly by using exchange instead of retrieve, and flatMaping on the resulting Mono (that emits the whole server response).

Related

Apache Camel:RabbitMQ Requeing a message to the same queue causes duplication of message

I'm doing something like
from(rabbitmq:pollingQueue?prefetchSize=1&concurrentConsumer=10)
.process(pollingRequestStatus) // check status of the request, if not ready, requeue = true
.Choice
.when(requeue == true) // request not ready
.to(rabbitmq:pollingQueue)//back to the same queue
.endChoice
.otherwise
.to(proceedToSomethingElse)
.endChoice.end;
When the requeue happens, the message gets duplicated, is this an expected behavior when sending back the message to the same queue?
I have also tried something like the following as suggested but it does not work,the message seems to be just consumed and won't requeue
from(rabbitmq:pollingQueue? prefetchSize=1&concurrentConsumer=10)
.onException(NotReadyException.class)
.handled(true)
.setHeader(RabbitMQConstants.REQUEUE, constant(true))
.end()
.process(pollingRequestStatus) // check status of the request, if not ready, throw NotReadyEception
.to(proceedToSomethingElse);
The other two ways i have tried that at least does not create duplicates,
1.) on NotReadyExeption, send the message back to the pollingQueue
from(rabbitmq:pollingQueue? prefetchSize=1&concurrentConsumer=10)
.onException(NotReadyException.class)
.to(rabbitmq:pollingQueue)
//.delay(constant(8000)) //not sure why it throws error if i set delay
.end
.process(pollingRequestStatus); // check status of the request, if not ready, throw NotReadyEception
This works, however, it runs too quick, like instantly.
If i set delay(constant(number)), the following error is thrown,
Exception in thread "main" org.apache.camel.FailedToCreateRouteException: Failed to create route route13 at: >>> From [bla bla bla...]
at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:1062)
at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:196)
at org.apache.camel.impl.DefaultCamelContext.startRoute(DefaultCamelContext.java:984)
at org.apache.camel.impl.DefaultCamelContext.startRouteDefinitions(DefaultCamelContext.java:3401)
at org.apache.camel.impl.DefaultCamelContext.doStartCamel(DefaultCamelContext.java:3132)
at org.apache.camel.impl.DefaultCamelContext.access$000(DefaultCamelContext.java:183)
at org.apache.camel.impl.DefaultCamelContext$2.call(DefaultCamelContext.java:2961)
at org.apache.camel.impl.DefaultCamelContext$2.call(DefaultCamelContext.java:2957)
at org.apache.camel.impl.DefaultCamelContext.doWithDefinedClassLoader(DefaultCamelContext.java:2980)
at org.apache.camel.impl.DefaultCamelContext.doStart(DefaultCamelContext.java:2957)
at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
at org.apache.camel.impl.DefaultCamelContext.start(DefaultCamelContext.java:2924)
at com.mbww.ithink.runner.Main.main(Main.java:174)
Caused by: java.lang.IllegalArgumentException: Route route13 has no output processors. You need to add outputs to the route such as to("log:foo").
at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:1060)
2.) On NotReadyException, redeliver based on redeliveryPolicy
from(rabbitmq:pollingQueue? prefetchSize=1&concurrentConsumer=10)
.onException(NotReadyException.class)
.setFaultBody(constant(false))
.maximumRedeliveries(-1) // -1 = redeliver forever
.redeliveryDelay(10000)
.end
.process(pollingRequestStatus); // check status of the request, if not ready, throw NotReadyEception
Originally the idea of requeue is so that if the request is not ready, requeue the message to be back of the queue, set a delay and check the status of the next request, and avoid getting things like Ratelimit error.
seems like the redelivery policy is the way to go now.
Thanks
To be able to requeue the message you have to turn off RabbitMQ's auto-acknowledgement. In that case you have to manually send the ack, nack or reject message back to the publisher. (https://www.rabbitmq.com/confirms.html)
It means you have to manually call one of the basicAck, basicNack or basicReject function on the current Channel implementation.
Translated to Camel:
To turn of auto-acknowledgement add the autoAck=false to the endpoint parameters.
AFAIK, the Camel Endpoint's underlying Channel is not accessible (source) so you can't directly call the Channel's basicReject(long deliveryTag, boolean requeue) function, but Camel does call it when the exchange is failed (exception occured during to routing).
The workaround might be the following: (pseudocodish and I haven't tried it, but based on checking the camel-rabbitmq endpoint's source, especially this part)
Updated workaround (tested and working):
from("rabbitmq://localhost:5672/first?queue=test&concurrentConsumers=10prefetchSize=1&autoAck=false&autoDelete=false")
.onException(NotReadyException.class)
.log("Error for ${body}! Requeue")
.asyncDelayedRedelivery().redeliveryDelay(5000) // wait 5 secs to redeliver and requeue
.maximumRedeliveries(1)
.setHeader(RabbitMQConstants.REQUEUE, constant(true))
.handled(true)
.setFaultBody(constant(true))
.end()
.log("Received: ${body}")
.process((e) -> {
if(notReady(e))
throw new NotReadyException(); // create a new Exception and throw it if the status is not ready
}
})
.to("direct:somethingElse");
Also I created a gist which implements almost a same scenario.
Hope it helps!
You don't need to send the message again, just set rabbitmq.REQUEUE property to true. If this property is set, rabbitmq component will automatically requeue the message instead of discarding it. From the docs:
Camel 2.14.2: This is used by the consumer to control rejection of the
message. When the consumer is complete processing the exchange, and if
the exchange failed, then the consumer is going to reject the message
from the RabbitMQ broker. The value of this header controls this
behavior. If the value is false (by default) then the message is
discarded/dead-lettered. If the value is true, then the message is
re-queued.
So, inside your processor you can do something like:
exchange.getIn().setHeader("rabbitmq.REQUEUE", true);
And then just check REQUEUE header == false in your route to call proceedToSomethingElse.

How can I make Feathers (Express-based API Framework) Return Error Responses

I've read the Feathers book, so I know that to create an error response I simply instantiate the appropriate feathers-errors class:
import {BadRequest} from 'feathers-errors';
const errorResponse = new BadRequest(`foo`, `bar`);
However, I'm having difficulty returning that error response to the user. Even when I create an endpoint that does nothing but return an error response ...
class SomeService {
create(data) {
return new BadRequest(`foo`, `bar`);
}
}
it doesn't work: instead of getting an error response, I get no response, and inside the Chrome debugger I can see that the response is pending (until it eventually times out and becomes an ERR_EMPTY_RESPONSE).
I tried reading about Express error handling, and in the examples I saw people used next to wrap the the response. However, next comes from the error handler, and I'm not sure where in my Feathers code I can get that next function.
If anyone could help explain (using next or not) how I can return a complete, not pending, error response, I would greatly appreciate it.
Your services have to return a promise. If your code is not asynchronous you can turn it into a promise with Promise.resolve and Promise.reject:
class SomeService {
create(data) {
return Promise.reject(new BadRequest(`foo`, `bar`));
}
}
Also make sure you registered the Express error handler to get nicely formatted errors:
const errorHandler = require('feathers-errors/handler');
// Last in the chain
app.use(errorHandler);
There is also more information in the error handling chapter.

How to return values in Invoke json response

I am trying to design a hyperledger chaincode, that is accessed through a web API, which passes json objects to the code. However, whenever I do an invoke method, I cannot actually return values to the user in the json response.
For instance, here is some sample code:
func (t *TLCChaincode) Invoke(stub *shim.ChaincodeStub, function string, args []string) ([]byte, error) {
//Do some stuff
return []byte("Some string"), nil
}
And some sample code for returning an error
func (t *TLCChaincode) Invoke(stub *shim.ChaincodeStub, function string, args []string) ([]byte, error) {
//Try to do some stuff
//Get some sort of error
return nil, errors.New("someError")
}
however both of these return a message like this, with the message always being some random character string like below (I suspect a hash of some sort):
{
"jsonrpc": "2.0",
"result": {
"status": "OK",
"message": "1e1123e3-b484-4120-a28e-c3a8db384557"
},
"id": 11
}
As you can see, this response contains neither the response I returned (as in the first case), or the error I returned (in the second case). How would I go about getting the returned bytes, or the returned error into the returned json?
Edit: Please note that if I call an invoke method from another chaincode, it receives the correct return values. It's only when it's returned to the user that it fails to work properly.
“Invoke” is not a synchronous call. Peer generates this OK message immediately when it receives your Web request.
Later, when Validation peers will try to generate new block, this “invoke” method will be executed together with other cached transactions.
In its turn chaincode-to-chaincode calls are synchronous and executed simultaneously.
As a workaround we use another Query request to check the status of this submitted Invoke. It would be great if anybody can propose better solution.
If you need to get a return value as soon as the Invoke is processed (included in a block), your best bet is to use some events (for the moment I guess).
In your chaincode, just setup the event with:
func (stub *ChaincodeStub) SetEvent(name string, payload []byte) error
GoDoc
You may be able to listen for events in your application using the SDK or protobuf messages directly. I'm doing it like this on the developer preview; but it seems that the standard way to retrieve Invoke result is to poll the blockchain via Queries.
There is a related GitHub issue here.

Setting Http Status Code and customized status message and returning JSON output using Jersey in RESTful Service

I have implemented a RESTful service using Jersey. I am able to return the desired output in JSON format. But, I also need to set Http Status Code and my customized status message. Status code and status message should not be part of the JSON output.
I tried following links:
JAX/Jersey Custom error code in Response
JAX-RS — How to return JSON and HTTP status code together?
Custom HTTP status response with JAX-RS (Jersey) and #RolesAllowed
but I am able to perform only one of the tasks, either returning JSON or setting HTTP status code and message.
I have code something like below:
import javax.ws.rs.core.Response;
public class MyClass(){
#GET
#Produces( { MediaType.APPLICATION_JSON })
public MyObject retrieveUserDetails()
{
MyObject obj = new MyObject();
//Code for retrieving user details.
obj.add(userDetails);
Response.status(Status.NO_CONTENT).entity("The User does not exist").build();
return obj;
}
}
Can anyone provide solution to this?
the mistakes are :
1. if status is set to NO_content (HTTP204) the norm is to have an entity empty. so entity will be returned as empty to your client. This is not what you want to do in all case, if found return details, if not found return 404.
2.Produces( { MediaType.APPLICATION_JSON }) tells that you will return a json content, and the content of entity is not a json. You will have to return a json. You will see I use jackson as it's part of Jersey.
set a #Path("/user") to set a endpoint path at least at Resource level.
Need to set a path in order to adress your resource (endpoint)
use a bean in order to pass multiple things. I've made an example bean for you.
as improvement caution with HTTP return, use the proper one
404 :not found resource
204 : empty....
take a look at the norm: http://www.wikiwand.com/en/List_of_HTTP_status_codes
Take a look the complete code in Gist: https://gist.github.com/jeorfevre/260067c5b265f65f93b3
Enjoy :)

Uncaught SyntaxError: Unexpected token B on live but not local server

So i am making some ajax post and it seems to work fine on the localhost, but when I publish it to ec2 server on amazon, I get Uncaught SyntaxError: Unexpected token B. Which seems to point to JSON parsing failure. Exact same database, same browser, and same methods being called. Why would it work on local and not on the server.
$.ajax({
url: '#Url.Action("Action")',
type: "POST",
data: ko.toJSON(viewModel),
dataType: "json",
contentType: "application/json; charset:utf-8",
success: function (result) {
},
error: function (xhr, textStatus, errorThrown) {
var errorData = $.parseJSON(xhr.responseText);
var errorMessages = [];
for (var key in errorData)
{
errorMessages.push(errorData[key]);
}
toastr.error(errorMessages.join("<br />"), 'Uh oh');
}
});
Here is the basic layout on the server side:
[HttpPost]
public JsonResult Action(ViewModel model)
{
try
{
Response.StatusCode = (int)HttpStatusCode.OK;
return Json("Successfull");
}
catch (Exception ex)
{
logger.Log(LogLevel.Error, string.Format("{0} \n {1}", ex.Message, ex.StackTrace));
Response.StatusCode = (int)HttpStatusCode.BadRequest;
List<string> errors = new List<string>();
errors.Add(ex.Message);
return Json(errors);
}
}
Within the try statement, I do a couple of queries to the database and post some calculations on Authorize.Net (https://api.authorize.net/soap/v1/Service.asmx)
If there are any error with Authorize.net web service calls then I return errors like this:
if (profile.resultCode == MessageTypeEnum.Error)
{
logger.Log(LogLevel.Error, string.Join(",", profile.messages.Select(x => x.text)));
Response.StatusCode = (int)HttpStatusCode.BadRequest;
List<string> errors = new List<string>();
profile.messages.ToList().ForEach(x => errors.Add(x.text));
db.SaveChanges();
return Json(errors);
}
This error that I am logging:
A public action method 'AddPromoCode' was not found on controller 'Flazingo.Controllers.PositionController'. at
System.Web.Mvc.Controller.HandleUnknownAction(String actionName) at
System.Web.Mvc.Controller.ExecuteCore() at
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) at
System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.b__5() at
System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.b__0() at
System.Web.Mvc.MvcHandler.<>c__DisplayClasse.b__d() at
System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean&
completedSynchronously)
You have another post at can't find action only on live server, works fine in local server, so I'm guessing that this post is specifically related to the javascript pieces, not the server-side pieces.
It sounds like something bad happens on the server, the server sends back some type of error, and the your error handler (in javascript) dies when trying to handle that response.
I get Uncaught SyntaxError: Unexpected token B. Which seems to point
to JSON parsing failure.
That sounds quite reasonable. Let's look at the code:
.ajax({
...
error: function (xhr, textStatus, errorThrown) {
var errorData = $.parseJSON(xhr.responseText);
var errorMessages = [];
...
},
...
});
I would highly recommend taking a look at what xhr.responseText is. My guess it that it does not contain valid JSON, so the parseJSON method throws the 'Unexpected token B' error.
To look at this value, you could put console.log(xhr.responseText); or you could use a tool like the javascript debugger in your web browser or fiddler to see what is there.
My guess is that the server is sending back a string with something like There was an error on the server instead of JSON like you are expecting. I see that you have error handling built in - my guess is that there is an error within your error handling, and there is nothing to catch it. I would recommend doing debugging on the server side to see if there is an error somewhere that you are not expecting.
Perhaps profile.messages is something that can only be enumerated once, and when you try to do it again it throws an error. Or maybe DB.SaveChanges is throwing an error for some reason. Either of these would result in the logged message that you see with the behavior you see on the client side.
You are attempting to return a 400 response (Bad Request) with your own custom response content.
I think that IIS by default doesn't allow you to do this, and as CodeThug mentioned, may be replacing your custom JSON content with a server message.
But it appears that you can override this behaviour:
http://develoq.net/2011/returning-a-body-content-with-400-http-status-code/
<system.webServer>
<httpErrors existingResponse="PassThrough"></httpErrors>
</system.webServer>
I have received similar mysterious errors in the past when using ASP.NET script bundling on knockout and bootstrap, especially when including the already-minified versions in a bundle.
If you are running in DEBUG mode on localhost, then ASP.NET will not be minifying the javascript libraries. However, once you deploy, you are presumably no longer in DEBUG mode and now minifying/bundling the scripts. Sometimes the bundling/minification of these scripts can result in syntax errors similar to the one you posted.
If so, you may be able to load knockout from a CDN to avoid the need for bundling.
It seems JSON sending as the response from the server is badly generated
ex: if a value in the database is hi "my" friends
JSON file will be generated as text:"hi "my" friends"
so value for property text is badly generated.
double check values in production/development server for such values.
best practice is replace quotes with escape character
ex: text:"hi \"my\" friends"