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 akka-user+unsubscr...@googlegroups.com.
To post to this group, send email to akka-user@googlegroups.com.
Visit this group at https://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.

Reply via email to