I am using web sockets with Play Framework in Scala. I would like to use Try/Catch functionality in my project for catching some Exceptions like Server Exception, Network Exception and etc.
What I did :
WebSocketController.scala
object LoginWS {
def props(out: ActorRef) = Props(new LoginWS(out))
}
class LoginWS(out: ActorRef) extends Actor {
def receive = {
case json_req: JsObject =>
var user_name = (json_req \ "user_name").as[String]
var password = (json_req \ "password").as[String]
var source = (json_req \ "source_type").as[String]
var result = UserLogin.authenticateUser(user_name, password).isDefined
var userID: Int = 0;
if(result) {
userID = UserLogin.getUserRole(user_name, password)
val login_status : String = "Success"
out ! Json.toJson(JsObject(Seq("login_status" -> JsString(login_status), "user_id" -> JsNumber(userID))))
}
else {
val login_status : String = "Failure"
out ! Json.toJson(JsObject(Seq("login_status" -> JsString(login_status), "user_id" -> JsNumber(userID))))
}
}
}
object WebSocketController extends Controller {
def login = WebSocket.acceptWithActor[JsValue, JsValue] { request =>
out => LoginWS.props(out)
}
}
What I tried :
I have used this answer posted by Ende Neu but it shows not found: value APIAction. Note: I added APIAction in routes file too
Code :
class LoginWS(out: ActorRef) extends Actor {
def receive = APIAction { request
case json_req: JsObject =>
.....
....//code here
}
}
object WebSocketController extends Controller {
def login = WebSocket.acceptWithActor[JsValue, JsValue] { request =>
out => LoginWS.props(out)
}
def APIAction(f: Request[AnyContent] => Result): Action[AnyContent] =
Action { request =>
Try(f(request))
.getOrElse(
InternalServerError(Json.obj("code" -> "500", "message" -> "Server error"))
)
}
}
Please help me to implement Try/Catch functionality in Web socket
Login request is handled within actor and I think you should handle your errors there. If you want to catch all exceptions instead of explicitly handling what could go wrong, I suggest doing the following
object SomeUtils {
def catchAll[A](out: ActorRef)(f: => A): Unit = {
val message = Try(f).getOrElse(Json.obj("code" -> "500", "message" -> "Server error"))
out ! message
}
}
import SomeUtils._
class LoginWS(out: ActorRef) extends Actor {
def receive = {
case json_req: JsObject => catchAll(out) {
val userName = (json_req \ "user_name").as[String]
val password = (json_req \ "password").as[String]
val authenticated = UserLogin.authenticateUser(userName, password).isDefined
if (authenticated) {
val role = UserLogin.getUserRole(userName, password)
Json.obj("login_status" -> "Success", "result" -> role)
}
else {
Json.obj("login_status" -> "Failure", "result" -> 0)
}
}
}
}
When handling jsonReq use catchAll method that would expect to get a receiver of the result and this result that could throw an Exception. In case of Exception it would use a default message with internal server error, and send this message to the receiver.
You could also make the out parameter implicit to skip putting it everywhere. Also your code is not really in scala style. Using vars etc...
If json sent to WS couldn't be parsed it would throw exception before reaching your code, solution can be found here:
How do I catch json parse error when using acceptWithActor?
Related
I want to refactor this code into something more readable and better in general. I know that in Scala there are normally neat ways of doing things but for me it's getting a bit messy (BTW I'm using the Play library in the code). this is a snippet of my code:
class HomeController #Inject()
(cc: ControllerComponents)
(implicit val config: Configuration)
extends AbstractController(cc) {
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {
implicit request: Request[String] =>
val promise = Promise[Result]()
Future {
val removeId = request.body.toLong
println(s"remove id $removeId")
promise completeWith {
idElementsDAO.remove(removeId, uid.toLong) map {
_ => Ok("")
} recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
BadRequest(errorMessage)
}
}
} recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
promise.success(BadRequest(errorMessage))
}
promise.future
}
}
Assuming that idElementsDAO.remove return a Future, this is probably more idiomatic:
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {implicit request =>
val removeId = request.body.toLong
println(s"remove id $removeId")
idElementsDAO.remove(removeId, uid.toLong)
.map(_ => NoContent) // probably more correct than `Ok("")`
.recover {
case t: Throwable =>
val errorMessage: String = getMessageFromThrowable(t)
println("remove id element failure " + errorMessage)
BadRequest(errorMessage)
}
}
No need for the Promise or the call to Future {...} (Future.apply).
Keep in mind, it's probably not the best idea to directly pass the underlying error of any Throwable directly to the http client (browser?).
If you add generic error handling code to the global error handler (for unexpected errors) that logs the error and sends a generic message to the front-end, you can then write it even cleaner like this:
def removeIdElement(uid: String) =
HAction(uid, "AuthEvent", 1, "login", parse.text).async {implicit request =>
val removeId = request.body.toLong
println(s"remove id $removeId")
for {
_ <- idElementsDAO.remove(removeId, uid.toLong)
} yield NoContent
}
https://www.playframework.com/documentation/2.6.x/ScalaErrorHandling
Here is a simpler version of your code:
class HomeController #Inject()(cc: ControllerComponents)(implicit val config: Configuration)
extends AbstractController(cc) {
def removeIdElement(uid: String) = HAction(uid, "AuthEvent", 1, "login", parse.text).async {
implicit request: Request[String] =>
Future {
val removeId = request.body.toLong
println(s"Removing id $removeId")
removeId
}.flatMap(id => idElementsDAO.remove(id, uid.toLong))
.map(_ => Ok(""))
.recover {
case t: Throwable =>
val errorMessage = getMessageFromThrowable(t)
println(s"Removing id element failed: ${errorMessage}")
BadRequest(errorMessage)
}
}
}
In the above code, a Promise is not needed, and the recover combinator is not repeated.
According to the Play framework documentation, we have the option of overriding the postStop method, but it has no ActorRef. I need an ActorRef because I am using ActorRef as an identifier in a HashMap containing the mappings of actors to connected clients: on disconnect, I want to remove that mapping from the HashMap.
Edit:-
Here HashMap works as an authentication pool.The very first message from client is for Authentication, and on validation the instance of ActorRef is added to HashMap. On the following events/messages authorization is verified by checking the existence of ActorRef in HashMap, have a look at the following code:-
def authenticate(actorRef: ActorRef, message: SocketParsedMessage) {
(message.data \ "token").validate[String] match {
case s: JsSuccess[String] => {
val token = jwt.parse(s.get)
if (jwt.verify(token,jwtSecret)) {
val userId = UUID.fromString(jwt.getSubject(token))
hashMapU2A += (UUID.fromString(jwt.getSubject(token)) -> actorRef)
hashMapA2U += (actorRef -> userId)
actorRef ! SocketParsedMessage(AllowedSocketMessageTypes.AUTHENTICATE, Json.obj(
"success" -> true, "message" -> "Authorized for making further requests request")).toString
publishUserStatus(userId)
} else {
actorRef ! SocketParsedMessage(AllowedSocketMessageTypes.AUTHENTICATE, JsObject(
Seq("success" -> JsBoolean(false), "message" -> JsString("Invalid token"))
)).toString
}
}
case e: JsError => {
actorRef ! SocketParsedMessage(AllowedSocketMessageTypes.AUTHENTICATE, Json.obj(
"success" -> false, "message" -> "Token not supplied with request")).toString
actorRef ! PoisonPill
}
}
}
val hashMapA2U: mutable.HashMap[ActorRef, UUID] = mutable.HashMap()
My Stupidity.
Actually, ActorRef is already provided in my custom implementation of Actor, and I can use it.
class MyWebSocketActor(sh: HandleSocket, out: ActorRef) extends Actor {
def receive = {
case msg: String => {
Json.fromJson[SocketParsedMessage](Json.parse(msg)) match {
case s: JsSuccess[SocketParsedMessage] => {
sh.HandleSocketMessages(out, s.get)
}
case _: JsError => {
out ! PoisonPill
}
}
// println(parsedMsg)
// out ! (msg)
}
}
override def postStop(): Unit = {
// super.postStop()
sh.clientDisconnected(out)
}
}
I am using akka-http and trying to log a request on a specific path using logrequest :
path(Segment / "account") { id =>
logRequest("users/account", Logging.InfoLevel) {
post {
entity(as[Account]) { account => ???
complete(HttpResponse(StatusCodes.NoContent))
}
}
}
however on my log I see something like
HttpRequest(HttpMethod(POST),https://localhost:9009/api/users/123/account,List(Host: localhost:9009, User-Agent: akka-http/10.0.6, Timeout-Access: <function1>),HttpEntity.Chunked(application/json),HttpProtocol(HTTP/1.1))
what I am looking for is the exact request including the body (json) as it was sent by the requestor.
The "HttpEntity.Chunked(application/json)" segment of the log is the output of HttpEntity.Chunked#toString. To get the entire request body, which is implemented as a stream, you need to call HttpEntity#toStrict to convert the Chunked request entity into a Strict request entity. You can make this call in a custom route:
def logRequestEntity(route: Route, level: LogLevel)
(implicit m: Materializer, ex: ExecutionContext) = {
def requestEntityLoggingFunction(loggingAdapter: LoggingAdapter)(req: HttpRequest): Unit = {
val timeout = 900.millis
val bodyAsBytes: Future[ByteString] = req.entity.toStrict(timeout).map(_.data)
val bodyAsString: Future[String] = bodyAsBytes.map(_.utf8String)
bodyAsString.onComplete {
case Success(body) =>
val logMsg = s"$req\nRequest body: $body"
loggingAdapter.log(level, logMsg)
case Failure(t) =>
val logMsg = s"Failed to get the body for: $req"
loggingAdapter.error(t, logMsg)
}
}
DebuggingDirectives.logRequest(LoggingMagnet(requestEntityLoggingFunction(_)))(route)
}
To use the above, pass your route to it:
val loggedRoute = logRequestEntity(route, Logging.InfoLevel)
In the following Controller, Authenticated extracts the token from the request headers and invokes a given action if and only if the token is valid (the code has been simplified for clarity):
object MyController extends Controller {
def Authenticated(action: Token => EssentialAction) = EssentialAction { requestHeader =>
val jwt = requestHeader.headers.get(HeaderNames.AUTHORIZATION) match {
case Some(header) => s"""$AuthScheme (.*)""".r.unapplySeq(header).map(_.head.trim)
case _ => requestHeader.getQueryString("auth").map(UriEncoding.decodePath(_, SC.US_ASCII.name))
}
jwt match {
case Some(t) if t.isValid =>
val token: Token = authService.token(t)
action(token)(requestHeader)
case _ => Done(Unauthorized.withHeaders(HeaderNames.WWW_AUTHENTICATE -> AuthScheme))
}
}
def getUser(userId: String) = Authenticated { token =>
Action.async { request =>
userService.find(userId).map {
case Some(user) => Ok(Json.obj("user" -> user.asJson)).withHeaders(
"token" -> authService.renew(token).asJson.toString
)
case _ => NotFound
}
}
}
}
The token returned by authService.token(t) is a JWT (JSON Web Token) and it can be used only once... so I need to return a new token after each request. The idea would be to put the new token in the response headers. That said, is there a way to add the token header to every response without having to invoke withHeader in each action?
Simply you can create a Filter and in Global.scala add WithFilters class.
import play.api.mvc._
object Global extends WithFilters(TokenFilter) {
...
}
Here is a Filter sample for logging so you could change it easily to satisfy your needs.
val loggingFilter = Filter { (next, rh) =>
val start = System.currentTimeMillis
def logTime(result: PlainResult): Result = {
val time = System.currentTimeMillis - start
Logger.info(s"${rh.method} ${rh.uri} took ${time}ms and returned ${result.header.status}")
result.withHeaders("Request-Time" -> time.toString)
}
next(rh) match {
case plain: PlainResult => logTime(plain)
case async: AsyncResult => async.transform(logTime)
}
}
I'd use ActionComposition. In Java it could look like:
public class YourActionComposition extends Action<YourAnnotation> {
#With(YourActionComposition.class)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface YourAnnotation {
}
public F.Promise<Result> call(Http.Context ctx) throws Throwable {
Promise<Result> call = delegate.call(ctx);
// Add something to your headers here
return call;
}
}
I'm using the example code from Play's ScalaOAuth documentation to authenticate to the twitter API. My full code is here:
object Twitter extends Controller {
val KEY = ConsumerKey("redacted")
val TWITTER = OAuth(ServiceInfo(
"https://api.twitter.com/oauth/request_token",
"https://api.twitter.com/oauth/access_token",
"https://api.twitter.com/oauth/authorize", KEY),
true)
def authenticate = Action { request =>
request.getQueryString("oauth_verifier").map { verifier =>
val tokenPair = sessionTokenPair(request).get
// We got the verifier; now get the access token, store it and back to index
TWITTER.retrieveAccessToken(tokenPair, verifier) match {
case Right(t) => {
// We received the authorized tokens in the OAuth object - store it before we proceed
Redirect(routes.Application.timeline).withSession("token" -> t.token, "secret" -> t.secret)
}
case Left(e) => throw e
}
}.getOrElse(
TWITTER.retrieveRequestToken("http://localhost:9000/auth") match {
case Right(t) => {
// We received the unauthorized tokens in the OAuth object - store it before we proceed
Redirect(TWITTER.redirectUrl(t.token)).withSession("token" -> t.token, "secret" -> t.secret)
}
case Left(e) => throw e
})
}
def sessionTokenPair(implicit request: RequestHeader): Option[RequestToken] = {
for {
token <- request.session.get("token")
secret <- request.session.get("secret")
} yield {
RequestToken(token, secret)
}
}
}
I spin up an actor that I want to use to search the Twitter API. I also use an edited version of their controller in that documentation to do this:
class TwitterParse extends Actor {
private var buildURL: String = _
private val urlBoilerPlate = "https://api.twitter.com/1.1/users/search.json?q="
private val pageCount = "&page=1&count=3"
def receive = {
case rawName: (Long, String) => {
buildURL = urlBoilerPlate + rawName._2 + pageCount
Twitter.sessionTokenPair match {
case Some(credentials) => {
WS.url(buildURL)
.sign(OAuthCalculator(Twitter.KEY, credentials))
.get
.map(result => println(result.json))
}
}
}
When I compile I get: "Cannot find any HTTP Request Header here" on the Twitter.sessionTokenPair call. This probably has to do with the fact that in their docs they include a
Action.async { implicit request =>
before calling sessionTokenPair. How would I fix this so it works in the Actor?
Thanks in advance