How to handle Akka HTTP client response failure - akka-http

Akka HTTP client requests return Future[HttpResponse] - how should one handle the Future failing? Just log an error or re-throw it to the supervisor?
Is there documentation of the type of errors that can be returned by thrown by the client (and hence automatically propagated to the supervisor ) as well as errors that can cause the Furure to fail.

It's matter of taste mostly. I typically convert Future[HttpResponse] to Future[Try[HttpResponse]] and then handle it as
response.flatMap { tryResp =>
tryResp match {
case Success(res) =>
res.status match {
case OK =>
// Unmarshal response here into Future[Something]
case Found =>
// Handle redirect by calling requestBlhBlah() again with anotehr URI
case _ =>
// I got status code I didn't expect so I wrap it along with body into Future failure
Unmarshal(res.entity).to[String].flatMap { body =>
Future.failed(new IOException(s"The response status is ${res.status} [${request.uri}] and response body is $body"))
}
}
case Failure(ex) =>
Future.failed(ex)
}
}
If you're using flow-based client you can also specify Decider to handle errors
val decider: Decider = {
case ex =>
ex.printStackTrace()
Supervision.Stop // Passes error down to subscriber
}
and then use it in either materializer
implicit val materializer = ActorMaterializer(ActorMaterializerSettings(system).withSupervisionStrategy(decider))(system)
or in per-flow basis via .withAttributes(ActorAttributes.supervisionStrategy(decider))
As per Future failure it's up to you how to handle it. You can convert failure to something else using recoverWith or log it in Future.onFailure.

Related

How can I fix an Axios interceptor causing property 'status' of undefined error

I have a selection to set permissions for elements to global or private. I'm using the Axios interceptor request to handle looking for the permissions field to have data and, if it does, stringify it. The problem is, it causes me to get a "TypeError: Cannot read property 'status' of undefined" when I attempt to reload the program at all. The only "fix" right now is to log out, remove the interceptor, log in, read it, and then run the check again.
Because of this, I can't even get to the home dashboard of the software. If I clear my cookies, I can go back to the login screen, but no further than that after attempting to log in.
Is there something I'm missing for it? below is the interceptor code. If more information or context is needed, please let me know.
export default {
install: (Vue) => {
Vue.$eventBus = new Vue();
Vue.axios.interceptors.response.use(response => {
return response.data;
}, async error => {
if (error.response.status === 401 && error.config.url != '/api/authentication/login') {
var config = await Vue.$configService.find();
window.location = config.accountPortalUrl;
return
}
console.log(error);
Vue.$eventBus.$emit('notifyUser', 'Uh oh, something went wrong!');
return Promise.reject(error);
});
Vue.axios.interceptors.request.use(
config => {
// check request method -> use post if many params
if (config.data.permissions) {
config.data.permissions = JSON.stringify(config.data.permissions);
}
console.log(config);
return config;
}
);
}
};
Looks like your service API is not responding, this might happen if the user is not authenticated . Your error is at line where you check (error.response.status). Its only possible to get an undefined response when the request was interrupted before response. Most probably if you check your browser network pannel you will see that the preflight check for this request causes a 401 network error. Hence because the preflight failed your actual response comes as undefined. You should sanity check first if your server responded with a response or not and then access the response status.
Something like this might help
if (error.response) {
// Request was made and the server responded successfully
// You can now de-structure the response object to get the status
console.log(error.response.status);
} else if (error.request) {
// request was made but not responded by server
console.log(error.request);
}
So, the answer ultimately was something extremely simple.
if (config.data.permissions)
needed to be
if (config.data && config.data.permissions)

Handling errors if no network is available

I just implemented my first backend file where I fetch some user data, messages and so on.
Now I wanted to include error handling if there is no network available.
I donĀ“t know if I did it right but this was my approach so far:
import axios from 'axios'
const host = process.env.VUE_APP_URL
export default {
person: async function (currentPerson) {
let params = {
currentPerson: localStorage.getItem("person"),
};
if (user) {
params['currentPerson'] = currentPerson;
}
return axios.get(`${host}/api/currentPerson`, {
params: params
})
//catching network errors
.catch (error => {
if (error.response) {
/*
* The request was made and the server responded with a
4xx/5xx error
*/
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
/*
* The request was made but no response was received
*/
console.log(error.request);
} else {
// Something happened in setting up the request and triggered an Error
console.log('Error', error.message);
}
console.log(error)
});
},
In my mounted() function of my main view I fetch the data from my backend file from above:
backend.matches().then(function (response) {
self.contacts = response.data.persons;
});
I tried to check in console if it is working but all I get is the following:
In the catch block I check for
response errors: like 4xx/5xx
request errors: if my network not responding in time
and any other errors
Would this be the right approach to check if a network is available or not? Or does it degrade the user experience when the user checks the error?
My backend file includes more methods.. do I have to write for each method these kind of requests?
In your backend file you don't react whether there is a network connection or not I think.
And only for reference: that is not the backend, but communicates with the backend - the backend is the part of your code what you communicate with, e.g. Laravel code, an API, ...
Try adding the following at the beginning of your catch part:
if (!error.response) {
//network error
console.log('No network connection');
} else if (error.response) {
//the rest of your code
This should print out No network connection in your console.
Run your application, turn off the internet connection and check the console.
These kind of code should always be located in your backend part.
My answer maybe different from your question.
When i create a .net core API with Angular i used three things to check is there network or not?
subscribe to windows's offline/online event
create signalR hub from layout component to API server
API request failed (it means lot of incident, but if 1. or 2. case is true i know what cause 3. case

akka http complete or redirect based on a future

This snipped of akka http server code works:
path("test") {
if (shouldRedirect())
redirect(redirectUrl, StatusCodes.PermanentRedirect)
else
complete("hello")
}
however, my shouldRedirect() returns a Future[Boolean]. I would need something like this, but returning a Future is not allowed here.
path("test") {
futureShouldRedirect().map { shouldRedirect =>
if (shouldRedirect)
redirect(redirectUrl, StatusCodes.PermanentRedirect)
else
complete("hello")
}
}
}
How to solve this?
ps: I am aware the the complete function accepts a Future inside, however this is not useful in this case I need to either return a complete or a redirect.
Akka provides predefined directives using you can solve the same.
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/
Solution Using onComplete Future Directive:
import akka.http.scaladsl.server.directives._
path("test") {
onComplete(shouldRedirect()) {
case Success(true) => redirect(redirectUrl, StatusCodes.PermanentRedirect)
case Success(false) => complete("hello")
case Failure(ex) =>
complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
The above solution will handle both success and failure cases in case if your shouldRedirect fails.
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onComplete.html
Solution Using onSuccess Future Directive:
import akka.http.scaladsl.server.directives._
path("test") {
onSuccess(shouldRedirect()) { isRedirect =>
{
if (isRedirect) redirect(redirectUrl, StatusCodes.PermanentRedirect)
else complete("hello")
}
}
}
The above solution will handle only success scenarios, in case of failure, it will be bubbled up to the exception handler in case if you have defined, else to default exception handler.
https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onSuccess.html

express middleware not sending response

I have a delete route that has 2 middleware functions Authcontroller.protect and authcontroller.restrictTo("admin, "lead-guide")
router
.delete(
authController.protect,
authController.restrictTo("admin", "lead-guide"),
tourController.deleteTour
);
Within restrictTo I have a check to see if the user has the proper "role" to perform that task and if they don't, express is supposed ot send a 403. I'm seeing that express never actually sends the response. it enters the if statement(i see the console successfully printing fialure) and then it just skips sending the response.
exports.restrictTo = (...roles) => {
return (req, res, next) => {
console.log(req.user.role);
if (!roles.includes(req.user.role)) {
console.log("failure");
return res.status(403).json({
status: fail,
message: "you do not have permission to perform this action"
});
console.log(req, res);
}
next();
};
};
Put a try/catch in your inner function and see if there's an exception being thrown.
From looking at your code, if the fail variable (which you don't show a definition for) is not defined, then that would throw an exception and keep the res.json() from executing.

Akka authenticateOAuth2Async: credentials missing

I have the following:
def myUserPassAuthenticator(credentials: Credentials): Future[Option[String]] = {
log.info(credentials.toString)
credentials match {
case p#Credentials.Provided(id) if p.verify("a") =>
log.info("Login success!")
Future.successful(Some(id))
case _ =>
log.info("Login failure!")
Future.successful(None)
}
}
val authRoute = path("login") {
authenticateOAuth2Async(realm = "secure site", myUserPassAuthenticator) { userName =>
complete(s"The user is '$userName'")
}
}
when navigating to that endpoint and entering credentials, the log line
log.info(credentials.toString)
just becomes Missing. What is wrong here?
The content-type of the request is "application/x-www-form-urlencoded"
and the data is "grant_type=password&username=INSERT_USERNAME_HERE&password=INSERT_PWD_HERE"
You should wrap your route in Route.seal directive:
val authRoute =
Route.seal {
path("login") {
authenticateOAuth2Async(realm = "secure site", myUserPassAuthenticator) { userName =>
complete(s"The user is '$userName'")
}
}
}
from documentation it seems to rely on default rejection 401 if authentication fails
Given a function returning Some[T] upon successful authentication and
None otherwise, respectively applies the inner route or rejects the
request with a AuthenticationFailedRejection rejection, which by
default is mapped to an 401 Unauthorized response.
and the Route.seal provides exactly that:
A Route can be "sealed" using Route.seal, which relies on the in-scope
RejectionHandler and ExceptionHandler instances to convert rejections
and exceptions into appropriate HTTP responses for the client.