spray.io debugging directives - converting rejection to StatusCodes - scala

I am using logRequestResponse debugging directive in order to log every request/response failing through whole path tree. Log entries looks as follows:
2015-07-29 14:03:13,643 [INFO ] [DataImportServices-akka.actor.default-dispatcher-6] [akka.actor.ActorSystemImpl]ActorSystem(DataImportServices) - get-userr: Response for
Request : HttpRequest(POST,https://localhost:8080/city/v1/transaction/1234,List(Accept-Language: cs, Accept-Encoding: gzip,...
Response: Rejected(List(MalformedRequestContentRejection(Protocol message tag had invalid wire type.,...
My root route trait which assembles all partial routes to one look as follows:
trait RestAPI extends Directives {
this: ServiceActors with Core =>
private implicit val _ = system.dispatcher
val route: Route =
logRequestResponse("log-activity", Logging.InfoLevel) {
new CountryImportServiceApi().route ~
new CityImportServiceApi().route
}
}
And partial routes are defined as following:
class CinemaImportServiceApi()(implicit executionContext: ExecutionContext) extends Directives {
implicit val timeout = Timeout(15 seconds)
val route: Route = {
pathPrefix("city") {
pathPrefix("v1") {
path("transaction" / Segment ) {
(siteId: String, transactionId: String) =>
post {
authenticate(BasicAuth(cityUserPasswordAuthenticator _, realm = "bd city import api")) {
user =>
entity(as[CityTrans]) { e =>
complete {
StatusCodes.OK
}
}
}
}
}
}
}
}
Assembled routes are run via HttpServiceActor runRoute.
I would like to convert rejection to StatusCode and log that via logRequestResponse. Even though I write a custom function for logging I get rejection. What seems to me fishy is that since it is wrapped the whole route tree rejection is still not converted to HttpResponse. In tests we are sealing the route in order to convert Rejection to HttpResponse. Is there a way how to mark a route as a complete route hence actually seal it? Or am I missing some important concept here?
Thx

I would add something like the following:
} ~ // This is the closing brace of the pathPrefix("city"), just add the ~
pathPrefix("") {
complete {
(StatusCodes.OK, "Invalid Route")
}
}
of course, change the OK and the body to whatever you need. pathPrefix("") will match any path that was rejected by any previous routes.

Related

ScalaPlay > 2.6 how to access POST requests while faking a trivial server in tests

I'm trying to setup a fake server with Play2.7 and the environment suggested by https://developer.lightbend.com/guides/play-rest-api/ just echoing json from a POST request. While I was able to make GET and POST requests returning hardwired values I can't access directly the request to return or process it. NOTE: this was doable with versions < 2.6 but now Action has become deprecated, so I'm wondering which is the correct way to deal with this in Play >= 2.6
I have read the following how to mock external WS API calls in Scala Play framework and How to unit test servers in Play 2.6 now that Action singleton is deprecated which are actually doing almost all I am trying to do, but it seems I need something different to access the Request. In previous version of Play I could do something like the following:
case POST(p"/route") => Action { request => Ok(request.body.asJson.getOrElse(JsObject.empty)) }
But it seems calling the action this way is not more possible since I received the 'infamous'
object Action in package mvc is deprecated: Inject an ActionBuilder (e.g. DefaultActionBuilder) or extend BaseController/AbstractController/InjectedController
error.
my actual working code is
object FakeServer {
def withServerForStep1[T](codeBlock: WSClient => T): T =
Server.withRouterFromComponents() { cs =>
{
case POST(p"/route") =>
cs.defaultActionBuilder {
Results.Ok(Json.arr(Json.obj("full_name" -> "octocat/Hello-World")))
}
}
} { implicit port =>
WsTestClient.withClient(codeBlock)
}
}
and the unit Spec is something like
"The step 1" should {
"Just call the fakeservice" in {
setupContext()
FakeServer.withServerForStep1 ( {
ws =>
val request = ws.url("/route")
val data = Json.obj(
"key1" -> "value1",
"key2" -> "value2"
)
val response = request.post(data).futureValue
response.status mustBe 200
response.body mustBe Json.toJson(data)
})
}
}
I would like to write the FakeServer in such a way that the Spec will succeed in checking that returned body is equal to original sent json. Currently it is obviously failing with
"[{"full_name":"octocat/Hello-World"}]" was not equal to {"key1":"value1","key2":"value2"}
I eventually found how to do it, and the correct way as often happens in Scala is... trivial.
The "trick" was just to add request => in the body of cs.defaultActionBuilder as in the next example
object FakeServer {
def withServerForStep1[T](codeBlock: WSClient => T): T =
Server.withRouterFromComponents() { cs =>
{
case POST(p"/route") =>
cs.defaultActionBuilder { request =>
val bodyAsJson = request.body.asJson.getOrElse(JsObject.empty)
Results.Ok(bodyAsJson)
}
}
} { implicit port =>
WsTestClient.withClient(codeBlock)
}
}
Then the test just needed to deal with possible extra wrapping quotes and reads as
val response = request.post(data).futureValue
response.status mustBe 200
response.body mustBe Json.toJson(data).toString()

Facing issue testing akka http cache

I am using Akka HTTP cache to cache my result. But i am facing issue to test it.
class GoogleAnalyticsController #Inject()(cache: Cache[String, HttpResponse],
googleAnalyticsApi: GoogleAnalyticsTrait,
googleAnalyticsHelper: GoogleAnalyticsHelper)
(implicit system: ActorSystem, materializer: ActorMaterializer) {
def routes: Route =
post {
pathPrefix("pageviews") {
path("clients" / Segment) { accountsClientId =>
entity(as[GoogleAnalyticsMetricsRequest]) { googleAnalyticsMetricsRequest =>
val googleAnalyticsMetricsKey = "key"
complete(
cache.getOrLoad(googleAnalyticsMetricsKey, _ => getGoogleAnalyticsMetricsData(accountsClientId, googleAnalyticsMetricsRequest))
)
}
}
}
}
private def getGoogleAnalyticsMetricsData(accountsClientId: String,
request: GoogleAnalyticsMetricsRequest) = {
val payload = generate(request)
val response = googleAnalyticsApi.googleAnalyticsMetricResponseHandler(accountsClientId, payload) // response from another microservice
googleAnalyticsHelper.googleAnalyticsMetricResponseHandler(
googleAnalyticsMetricsRequest.metricName, response)
}
}
class GoogleAnalyticsHelper extends LoggingHelper {
def googleAnalyticsMetricResponseHandler(metricName: String, response: Either[Throwable, Long]): Future[HttpResponse] =
response.fold({ error =>
logger.error(s"An exception has occurred while getting $metricName from behavior service and error is ${error.getMessage}")
Marshal(FailureResponse(error.getMessage)).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.InternalServerError))
}, value =>
Marshal(MetricResponse(metricName, value)).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.OK))
)
}
Test case: Sharing only the relevant part
"get success metric response for " + pageviews + " metric of given accounts client id" in { fixture =>
import fixture._
val metricResponse = MetricResponse(pageviews, 1)
val eventualHttpResponse = Marshal(metricResponse).to[HttpResponse].map(httpResponse => httpResponse.copy(status = StatusCodes.OK))
when(cache.getOrLoad(anyString, any[String => Future[HttpResponse]].apply)).thenReturn(eventualHttpResponse)
when(googleAnalyticsApi.getDataFromGoogleAnalytics(accountsClientId, generate(GoogleAnalyticsRequest(startDate, endDate, pageviews))))
.thenReturn(ApiResult[Long](Some("1"), None))
when(googleAnalyticsHelper.googleAnalyticsMetricResponseHandler(pageviews, Right(1))).thenReturn(eventualHttpResponse)
Post(s"/pageviews/clients/$accountsClientId").withEntity(requestEntity) ~>
googleAnalyticsController.routes ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual generate(metricResponse)
}
}
By doing this, I am best to test if the cache has the key but not able to test if cache misses the hit. In code coverage, it misses following highlighted part
cache.getOrLoad(googleAnalyticsMetricsKey, _ =>
getGoogleAnalyticsMetricsData(accountsClientId,
googleAnalyticsMetricsRequest))
If there is a design issue, please feel free to guide me on how can I make my design testable.
Thanks in advance.
I think you don't need to mock the cache. You should create an actual object for cache instead of mocked one.
What you have done is, you have mocked the cache, in this case, the highlighted part will be not called as you are providing the mocked value. In the following stubbing, whenever cache.getOrLoad is found, eventualHttpResponse is returned:
when(cache.getOrLoad(anyString, any[String => Future[HttpResponse]].apply)).thenReturn(eventualHttpResponse)
and hence the function getGoogleAnalyticsMetricsData(accountsClientId, googleAnalyticsMetricsRequest) is never called.

Adding headers to WSRequest in WS client from Play Framework

I have a service which requires authentication through headers. I have an existing java client which generates the values for me. I am trying to apply the headers to a ws request using a WSRequestHeaderFilter.
The code seems to run fine when I breakpoint and I can see the headers are applied. But in my Test Server (using the play SIRD router) the headers to not seem to be applied?
How can I make my required headers appear in the request using a filter or such method?
See the code below:
Filter:
class AuthenticatingFilter #Inject() (authHeaderGenerator: AuthHeaderGenerator) extends WSRequestFilter {
def apply(executor: WSRequestExecutor): WSRequestExecutor = {
new WSRequestExecutor {
override def execute(request: WSRequest): Future[WSResponse] = {
val headers = authHeaderGenerator.generateRequestHeaders(request.method, request.uri.toString, null).asScala.toList
executor.execute(request.withHeaders(headers:_*))
}
}
}
}
Usage in client:
//code omitted for brevity
def getStuff() = ws.url(s"${baseUrl}/authenticatedEndpoint").withRequestFilter(filter).get()
Test:
// code omitted for brevity
Server.withRouter() {
case GET(p"/authenticatedEndpoint") => Action {
request =>
request.headers.get(authHeader) match {
case Some(authHeaderValue) => Results.Ok(expectedResult)
case _ => Results.Forbidden
}
}
} {
implicit port =>
implicit val materializer = Play.current.materializer
WsTestClient.withClient {
client =>
val authenticatedClient: AuthenticatedClient = new AuthenticatedClient(client,filter)
val result: String = Await.result(authenticatedClient.getStuff(), Duration.Inf)
result must beEqualTo(expectedResult)
}
}
}
Thanks,
Ben
As it turned out this was a bug in play. I patched this and the change was merged into the master branch (https://github.com/playframework/playframework/pull/6077) so if you are having similar problems this should be fixed by upgrading to play 2.5.3 (when available)

Silhouette authorization using request data

I use Silhouette and Play 2.4 and I'd like to restrict actions if a SecuredRequest body contains something wrong.
I know, I should use trait Authorization as described by official docs.
I'm trying to do the following:
case class WithCheck(checkCriteria: String) extends Authorization[User, CookieAuthenticator] {
def isAuthorized[B](user: User, authenticator: CookieAuthenticator)(implicit request: Request[B], messages: Messages) = {
Future.successful(user.criteria == checkCriteria)
}
}
and than
def myAction = SecuredAction(WithCheck("bar")) { implicit request =>
val foo = ...// deserialize object from request.body
val checkCriteria = foo.criteria
// do something else here
}
How can I use the checkCriteria value in the class WithCheck?
I found a solution.
Somehow, I was blind to see that isAuthorized has the same request as an implicit parameter. So, the check could be done entirely into the isAuthorized. For example,
case class WithCheck() extends Authorization[User, CookieAuthenticator] {
def isAuthorized[B](user: User, authenticator: CookieAuthenticator)(implicit request: Request[B], messages: Messages) = {
val foo = upickle.read[Foo](request.body.toString())
Future.successful(user.criteria == foo.criteria)
}
}

Akka actor system with HTTP interface

I'm trying to create an Akka system which would among other things respond to HTTP requests. I created a few actors who exchange messages fine. I can also use akka-http to respond to HTTP requests. The problem is in connecting those two parts.
TL;DR: How to talk to Akka actors during akka-http request processing?
I created a single actor responsible for bringing up the HTTP system:
class HttpActor extends Actor with ActorLogging {
/* implicits elided */
private def initHttp() = {
val route: Route = path("status") { get { complete { "OK" } } }
Http()(context.system).bindAndHandle(route, "localhost", 8080)
}
private var bound: Option[Future[Http.ServerBinding]] = None
override def receive = {
case HttpActor.Init =>
bound match {
case Some(x) => log.warning("Http already bootstrapping")
case None =>
bound = Some(initHttp(watcher))
}
}
}
object HttpActor {
case object Init
}
As you may see, the actor creates the akka-http service on the first message it receives (no reason, really, it could do it in the constructor as well).
Now, during the request processing I need to communicate with some other actors, and I can't get it working.
My approach:
private def initInteractiveHttp() = {
val route: Route = path("status") {
get { complete { "OK" } }
} ~ path("ask") {
get { complete {
// Here are the interesting two lines:
val otherActorResponse = someOtherActor ? SomeMessage
otherActorResponse.mapTo[String]
} }
Http()(context.system).bindAndHandle(route, "localhost", 8080)
}
This would send a SomeMessage to someOtherActor and wait for response prior to completing the Request-Response cycle. As I understand, however, that messages would be sent from the root HttpActor, which is bad and leads nowhere in terms of scalability. Ideally I would create a distinct instance of specialized actor for every request, but this fails due to akka-http typing. Consider the following example:
class DisposableActor(httpContext: HttpContext) {
override def preStart() = {
// ... send some questions to other actors
}
override def receive = {
case GotAllData(x) => httpContext.complete(x)
}
}
class HttpActorWithDisposables {
// there is a `context` value in scope - we're an actor, after all
private def initHttpWithDisposableActors() = {
val route: Route = path("status") {
get { complete { "OK" } }
} ~ path("ask") {
get { httpContext =>
val props = Props(new DisposableActor(httpContext))
val disposableActor = context.actorOf(props, "disposable-actor-name")
// I have nothing to return here
}
}
Http()(context.system).bindAndHandle(route, "localhost", 8080)
}
With this approach, I can force the DisposableActor to call httpContext.complete at some point of time. This should correctly end the Request-Response processing cycle. The Route DSL, however, requires to return a valid response (or Future) inside the get block, so this approach doesn't work.
Your first approach is quite alright, actually.
The ask pattern creates a lightweight, disposable actor for you that waits on the result (non-blockingly) to complete the future. It basically does all the things you want to replicate with the DisposableActor and your main HttpActor is not stressed by this.
If you still want to use a different actor, there is completeWith:
completeWith(instanceOf[String]) { complete =>
// complete is of type String => Unit
val props = Props(new DisposableActor(complete))
val disposableActor = context.actorOf(props, "disposable-actor-name")
// completeWith expects you to return unit
}
And in you actor, call the complete function when you have the result
class DisposableActor(complete: String => Unit) {
override def receive = {
case GotAllData(x) =>
complete(x)
context stop self // don't for get to stop the actor
}
}