Ive been using the low level API but taking another look at the DSL .
I have a number of actors that all base on a class called EndpointActor and
have some basic facilities to handle errors, problems in auth and so on in
custom ways. I want to know if this is possible in the DSL. The base class
of my actors is like this:
abstract class EndpointActor(val services: NodeServices, val replyTo: ActorRef,
val requestId: UUID, val request: HttpRequest) extends Actor {
val start: Long = System.nanoTime
protected val log = Logging(context.system, this)
context.setReceiveTimeout(EndpointActor.DEFAULT_TIMEOUT)
override def receive: Receive = {
case ReceiveTimeout => onTimeout()
}
protected def bodyAsJsValue: Future[Option[JsValue]] = {
import context.dispatcher
request.entity.toStrict(3.seconds)(services.materializer).map(e => {
try {
val dataString = e.data.utf8String
if (dataString.isEmpty) None
else Option(Json.parse(e.data.utf8String))
} catch {
case e: JsonParseException =>
badRequest(JsString("Cannot parse request body: " + e.getMessage))
None
}
})
}
protected def withValidBody[A](function: Option[A] => Unit)(implicit reads:
Reads[A]): Unit = {
require(function != null)
import context.dispatcher
bodyAsJsValue.map {
case None => function.apply(None)
case Some(json) =>
json.validate[A] match {
case s: JsSuccess[A] => function.apply(Option(s.get))
case e: JsError =>
val errorsJson = JsError.toJson(e)
badRequest(errorsJson, errorsJson)
}
// todo check parsing issues
} recover {
case e: Exception =>
onException(e)
}
}
protected def withRequiredBody[A](function: A => Unit)(implicit reads:
Reads[A]): Unit = {
require(function != null)
withValidBody[A]({
case None => badRequest(JsString("Body Content Missing"))
case Some(decoded) => function(decoded)
})(reads)
}
protected def okSuccess[A](content: JsValue): Unit = {
request.discardEntityBytes(services.materializer)
val response = EndpointResponse.success(requestId, content)
replyTo.tell(response.toHttpResponse, self)
context.stop(self)
if (log.isDebugEnabled) log.debug(Json.prettyPrint(logDetail(response))) //
todo Change to debug mode
}
protected def okError[A](content: JsValue): Unit = {
val response = EndpointResponse.error(requestId, content)
replyTo.tell(response.toHttpResponse, self)
if (log.isDebugEnabled) log.debug(Json.prettyPrint(logDetail(response))) //
todo Change to debug mode
context.stop(self)
}
protected def forbidden(content: JsValue): Unit = {
request.discardEntityBytes(services.materializer)
val response = EndpointResponse.forbidden(requestId, content)
replyTo.tell(response.toHttpResponse, self)
if (log.isDebugEnabled) log.debug(Json.prettyPrint(logDetail(response)))
context.stop(self)
}
protected def badRequest[A](content: JsValue, logInfo: JsValue = JsNull):
Unit = {
// todo The ops people should be monitoring for many bad requests because
it could indicate hacking attempts
request.discardEntityBytes(services.materializer) // drop any content passed
val response = EndpointResponse.badRequest(requestId, content)
replyTo.tell(response.toHttpResponse, self)
if (log.isDebugEnabled) log.debug(Json.prettyPrint(logDetail(response,
logInfo)))
context.stop(self)
}
protected def notFound(): Unit = {
request.discardEntityBytes(services.materializer)
val response = EndpointResponse.notFound(requestId, request)
replyTo.tell(response.toHttpResponse, self)
if (log.isDebugEnabled) log.debug(Json.prettyPrint(logDetail(response)))
context.stop(self)
}
protected def onTimeout(): Unit = {
request.discardEntityBytes(services.materializer)
val response = EndpointResponse.timeout(requestId, request)
replyTo.tell(response.toHttpResponse, self)
log.error(Json.prettyPrint(logDetail(response)))
context.stop(self)
}
protected def onException(ex: Exception): Unit = {
request.discardEntityBytes(services.materializer) // drop any content passed
val response = EndpointResponse.internalError(requestId, request)
replyTo.tell(response.toHttpResponse, self)
log.error(ex, Json.prettyPrint(logDetail(response)))
context.stop(self)
}
private def logDetail(endpointResponse: EndpointResponse, logInfo: JsValue =
JsNull): JsValue = {
Json.obj(
"type" -> JsString(this.getClass.getName),
"requestUID" -> JsString(requestId.toString),
"elapsed" -> JsNumber(TimeFrame.elapsedSeconds(start)),
"uri" -> JsString(request.getUri.toRelative.toString),
"logInfo" -> logInfo,
"response" -> endpointResponse.toJson,
"debugInfo" -> debugInfo
)
}
protected def debugInfo: JsValue
}
This forms the base per-request actor. for my application. As each request
comes in, one of the sub-classes is started up and handles it and calls one
of the methods. The thing is the base class methods make it really easy on
the individual actors because they just have to call those methods to set
the response type and return a formatted message. The real reason for
errors is hidden from the web request because that gives info for hackers
to use.
What I would like to know is if there is some generic way in the DSL to say
"If the entity doesn't decode, send back bad request with json in this
format" without putting a handler on every endpoint in the DSL and without
embedding a ton of application logic in the DSL. Would rather the endpoint
not need to worry about these little detail. The DSL seems to be a bit
rigid that whenever it cant decode an entity, it returns a validation
rejection but I have no control over the HTTP code or the content of the
rejection. I don't want to tell users why their request is bad so they can
figure out how to hack the API. As an example, consider this demo code:
object DSLServerNode extends App {
object Foo {
implicit val fooFormat: Format[Foo] = Json.format[Foo]
}
case class Foo(bar: String)
implicit val system: ActorSystem = ActorSystem()
implicit val mat: ActorMaterializer = ActorMaterializer()
Http().bindAndHandle(route, "127.0.0.1", 8000)
StdIn.readLine("Hit ENTER to exit")
Await.ready(system.terminate(), Duration.Inf)
def route(implicit mat: Materializer): Route = {
import Directives._
import PlayJsonSupport._
pathSingleSlash {
post {
entity(as[Foo]) { foo =>
complete {
foo
}
}
}
}
}
}
If the JSON fails to decode to a FOO, the system sends back an error to the
HTTP request but I don't have control over the content of that error or the
HTTP response code. Similarly inside the route if foo had an illegal string
according to the business logic, for example lets say white space is
disallowed, then I would want to send back a bad request, not complete it
with HTTP OK.
Suggestions?
Thanks for your time.
--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ:
>>>>>>>>>> http://doc.akka.io/docs/akka/current/additional/faq.html
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka
User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.