Hi,

>> 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

For this, I would start by looking at the 'customizing rejection handling' 
section 
in 
http://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/rejections.html.
 
Essentially you need to figure out
1. What rejections occurs when decoding fails (e.g. ValidationRejection, 
MalformedRequestContentRejection, ...). You can see what akka is doing when 
handling entities in the source 
at akka.http.scaladsl.server.directives.MarshallingDirectives#entity.
2. Write a rejection handler with cases that catch these rejections and 
write the response you want.

>> 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.

I think I would
1. Create my business objects so that any illegal strings cause 
IllegalArgumentExceptions in their instantiation (so that your business 
logic is not mixed with akka-http concerns). This will in turn lead to 
ValidationRejections from akka. Then
2. Write a custom handler for ValidationRejection (as above).  

On Wednesday, 23 August 2017 17:44:03 UTC+1, kraythe wrote:
>
> 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