I am using scalatra to "export" a MongoDB data to JSon, my actions are very simple, like:
get("/") {
val title = db.get_collection("main", "api", "title")
send_json(title)
}
I want to send a HTTP error and a text if something go wrong, on the other side it will be converted to something meaninful to the user.
So the method becames:
get("/") {
try {
val title = db.get_collection("main", "api", "title")
send_json(title)
} catch {
case e:java.util.NoSuchElementException => send_error("DB malformed", InternalError)
case e:com.mongodb.MongoException => send_error("Can not connect to MongoDB", InternalError)
case e => send_error("Internal Error", InternalError)
}
}
The try catch is bigger that the actual method and I need to do this for every method, the class become at first sight an ugly collection of try catch.
There is any way to avoid or minimize the bad looking and distracting try catch all over the code? I am new to Scala (and Java BTW) so I guess I am missing something.
I dont want the DB object to send JSON, so having the try catch on the db.get_collection method is not an option.
There's a special route handler for this:
error {
case e: Throwable => {
redirect("/")
}
}
By changing the case statement you can switch on the error type.
Well, I don't know Scalatra enough, but the catch block is a partial function, so you could do something like this:
val errorHandling: PartialFunction[Throwable, Unit] = {
case e:java.util.NoSuchElementException => send_error("DB malformed", InternalError)
case e:com.mongodb.MongoException => send_error("Can not connect to MongoDB", InternalError)
case e => send_error("Internal Error", InternalError)
}
get("/") {
try {
val title = db.get_collection("main", "api", "title")
send_json(title)
} catch errorHandling
}
Related
So I want to write simple login logic in play-framework. Password checking looks like this
...
.filter(user => BCrypt.checkpw(req.password, user.password))
...
It's working no problem. But when the password is incorrect, I get the exception:
Future.filter predicate is not satisfied
However, I want to have my own exception to be thrown, so I did like this:
...
.map(user =>
if (!BCrypt.checkpw(req.password, user.password)) {
throw WrongCredentials()}
else user)
...
Which works, but I have been wondering if there is a cleaner way.
You can do something like(from the official doc):
val purchase: Future[Int] = rateQuote map {
quote => connection.buy(amount, quote)
} recover {
case QuoteChangedException() => 0
}
I am trying to count rejects that were returned by RejectionHandler
I guess the way I doing this now is not the best one, or even incorrect. I am just trying to invoke the incremental method in my database, in each of the handled cases.
implicit def rejectionHandler: RejectionHandler =
RejectionHandler.newBuilder()
.handle {
case MissingCookieRejection(cookieName) =>
requestInfoEntry.incrementRjectedNum
complete(HttpResponse(BadRequest, entity = "No cookies, no service!!!"))
}
.handle {
case AuthorizationFailedRejection =>
requestInfoEntry.incrementRjectedNum
complete((Forbidden, "You're out of your depth!"))
}
.handle {
case ValidationRejection(msg, _) =>
requestInfoEntry.incrementRjectedNum
complete((InternalServerError, "That wasn't valid! " + msg))
}
.handleAll[MethodRejection] { methodRejections =>
requestInfoEntry.incrementRjectedNum//todo sideeffect ??
val names = methodRejections.map(_.supported.name)
complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
}
.handleNotFound {
requestInfoEntry.incrementRjectedNum
complete((NotFound, "Not here bldghad!"))
}
.result()
While I "visit my unfound page", Akka HTTP returns me a right response: "Not here bldghad!" every time I refresh browser on not existed web-page. But when I check my database, I see the only one increment. Can I do this way as I do at all? (I need to count successes too)
PS Maybe I need to work with status codes and do not use side effects. But what is the best place where I can do it? I have a lot of controllers and do not want to intercept this in every controller)
Can I globally intercept responses somewhere?
I've done it this way, hope it would be useful
def rejectionHandlerWithCounter: RejectionHandler = { (rejections: Seq[Rejection]) =>
requestInfoEntry.incrementRjectedNum
Some(complete((StatusCodes.Forbidden)))
}
I have a block of code as shown below to handle some exceptions, i use if-else statement but i don't like them nested within each other, wondering if it is possible to use pattern match to make it nicer?
try {
if (response.code < 200 || response.code > 299) {
throw new SearchClientFailure(s"API request failed with code ${response.code}, body ${response.body}")
} else {
if (isExceeded(response.body)) {
throw new SearchClientFailure("Exceed limit")
} else {
response.body
}
}
} catch {
case e: SearchClientFailure =>
if (queries.isEmpty) {
throw new SearchClientFailure
} else {
logger.warn(s"Failed to update the queries: ${e.message}")
queries
}
case _ =>
throw new SearchClientFailure
}
You could do :
response match {
case r if (r.code < 200 || r.code > 299) => ...
case r if (isExceeded(r.body)) => ...
case r => r.body
}
Is it nicer ? I'm not 100% sure honestly, I don't really prefer this style to your one.
Btw, depending on what you used you often have access to response.isSuccess() or response.code.isSuccess() instead of testing code values
Rather than take on the overhead of those short throws and catches, I'd be tempted to use Either[String,Response].
Right(response).flatMap{r =>
if (r.code > 199 && r.code < 300) Right(r)
else Left(s"API request failed with code ${r.code}, body ${r.body}")
}.flatMap{r =>
if (isExceeded(r.body)) Left("Exceed limit")
else Right(r)
}.fold(msg => {
if (queries.isEmpty) throw new SearchClientFailure
logger.warn(s"Failed to update the queries: $msg")
queries
}, _.body)
The only throw required is the one tossed out of this context. Everything else is handled in the code flow.
Here is a version that uses Either
val apiResult: Either[String, String] =
if (response.code < 200 || response.code > 299)
Left(s"API request failed with code ${response.code}, body ${response.body}")
else if (isExceeded(response.body))
Left("Exceed limit")
else
Right(response.body)
apiResult match {
case Right(result) =>
result
case Left(message) if queries.nonEmpty =>
logger.warn(s"Failed to update the queries: $message")
queries
case _ =>
throw new SearchClientFailure
}
The apiResult value stores either the error string or the correct result of the API call. The subsequent match can then retrieve the original error string if required.
This follows the convention that Right is the normal/successful result and Left is the error case or abnormal result.
I am having a trouble with my scala code below:
class ClassMyHelper {
protected var logger: Logger = LogManager.getLogger(classOf[AvroHelper])
def somefunc(schema: Schema, datum: GenericRecord): Array[Byte] = {
<code>
var byteData: Array[Byte] = null
try {
<code>
byteData = os.toByteArray()
//byteData
} catch {
case e: IOException =>
logger.error("IOException encountered!! ", e)
case e: Exception =>
logger.error("Something went wrong!! ", e)
} finally try os.close()
catch {
case e: IOException =>
logger.error("IOException encountered while closing output stream!! ", e)
case e: Exception =>
logger.error("Something went wrong while closing output stream!! ", e)
}
byteData //Unreachable code
}
}
The problem is that the last line in the somefunc function I am getting an unreachable code error.
Can you please help me in identifying what am I doing wrong here.
If you add a finally {} after the 2nd catch block things appear to clear up. I'm not sure why. I never use try/catch/finally myself. I prefer the Scala Try class from the Standard Library.
BTW, next time you post code please include the required imports, and check to make sure your code compiles as presented.
I'm trying to implement "request based" sessions in scalaquery in the play framework. I create a session using scalaquery and attempt to store it in the current http context, as follows:
def withTransaction[A](bp: BodyParser[A])(f: Request[A] => Result): Action[A] = {
Action(bp) {
request =>
val context = Http.Context.current()
val session = createSession()
session.conn.setAutoCommit(false)
context.args.put("scalaquery.session", session)
try {
val result = f(request)
session.conn.commit()
result
}
catch {
case t: Throwable =>
session.conn.rollback()
throw t
}
finally {
session.close()
context.args.remove("scalaquery.session")
}
}
}
then i wrap my actions in my controllers like:
withTransaction(parse.anyContent) {
Action {
//code that produces a result here
}
}
However, it crashes in the following line saying:
val context = Http.Context.current()
[RuntimeException: There is no HTTP Context available from here.]
So, why is the context not available? This code is called directly by the framework, so shouldn't the context be set by the time this code executes? Or am i using the wrong way for accessing the context?
EDIT: The "session" is of type org.scalaquery.session.Session. The reason why i want to set it in the HttpContext is so that the wrapped actions can access it in an "http scoped" fashion, i.e. That each request stores their session separately, yet all services that need a session can find it in a public scope that is separated per request.
I think the problem is you're using the Java API with the Scala controller. Http.Context is only set if you're using the Java controller. Have you considered using the Scala Session API?
Also, another question is, why do you need to store the session in the context? I see you just remove it at the end anyway. If what you need is for the sub-actions to be able to access the session, you could just pass it in the function.
I'm just going to assume session is of type Session
def withTransaction[A](bp: BodyParser[A])(f: Session => Request[A] => Result): Action[A] = {
Action(bp) {
request =>
val session = createSession()
session.conn.setAutoCommit(false)
try {
val result = f(session)(request)
session.conn.commit()
result
}
catch {
case t: Throwable =>
session.conn.rollback()
throw t
}
finally {
session.close()
}
}
}
and your sub-actions would be
withTransaction(parse.anyContent) { session => request =>
//code that produces a result here
}
you don't need to wrap this in Action anymore since it's already wrapped by withTransaction