Failing service tests on Lagom 1.4.1

Hi here,

After upgrading Lagom up to 1.4.1. my services tests start failing with exception:

[info] The UserService
Uncaught error from thread [application-akka.actor.default-dispatcher-2]: play.core.server.Server$.getHandlerFor(Lplay/api/mvc/RequestHeader;Lplay/core/ApplicationProvider;)Lscala/util/Either;, shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[application]
java.lang.NoSuchMethodError: play.core.server.Server$.getHandlerFor(Lplay/api/mvc/RequestHeader;Lplay/core/ApplicationProvider;)Lscala/util/Either;
	at play.core.server.AkkaHttpServer.getHandler(AkkaHttpServer.scala:217)
	at play.core.server.AkkaHttpServer.handleRequest(AkkaHttpServer.scala:195)
	at play.core.server.AkkaHttpServer.$anonfun$createServerBinding$1(AkkaHttpServer.scala:106)
	at akka.stream.impl.fusing.MapAsync$$anon$25.onPush(Ops.scala:1194)
	at akka.stream.impl.fusing.GraphInterpreter.processPush(GraphInterpreter.scala:519)
	at akka.stream.impl.fusing.GraphInterpreter.processEvent(GraphInterpreter.scala:482)
	at akka.stream.impl.fusing.GraphInterpreter.execute(GraphInterpreter.scala:378)
	at akka.stream.impl.fusing.GraphInterpreterShell.runBatch(ActorGraphInterpreter.scala:585)
	at akka.stream.impl.fusing.GraphInterpreterShell$AsyncInput.execute(ActorGraphInterpreter.scala:469)
	at akka.stream.impl.fusing.GraphInterpreterShell.processEvent(ActorGraphInterpreter.scala:560)
	at akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:742)
	at akka.stream.impl.fusing.ActorGraphInterpreter$$anonfun$receive$1.applyOrElse(ActorGraphInterpreter.scala:757)
	at akka.actor.Actor.aroundReceive(Actor.scala:517)
	at akka.actor.Actor.aroundReceive$(Actor.scala:515)
	at akka.stream.impl.fusing.ActorGraphInterpreter.aroundReceive(ActorGraphInterpreter.scala:667)
	at akka.actor.ActorCell.receiveMessage(ActorCell.scala:590)
	at akka.actor.ActorCell.invoke(ActorCell.scala:559)
	at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
	at akka.dispatch.Mailbox.run(Mailbox.scala:224)
	at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
	at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
	at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
	at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
	at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
Exception in thread "Thread-57" java.io.EOFException
	at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2608)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1319)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
	at org.scalatest.tools.Framework$ScalaTestRunner$Skeleton$1$React.react(Framework.scala:818)
	at org.scalatest.tools.Framework$ScalaTestRunner$Skeleton$1.run(Framework.scala:807)
	at java.lang.Thread.run(Thread.java:745)

In the tests I create the server as described in docs:

private lazy val server = ServiceTest.startServer(
    ServiceTest.defaultSetup.withCassandra(true)
  ) { ctx =>
    new UserApplication(ctx) with LocalServiceLocator with TestTopicComponents {
      override def additionalConfiguration: AdditionalConfiguration =
        super.additionalConfiguration ++ Configuration.from(Map(
          "cassandra-query-journal.eventual-consistency-delay" -> "0"
        ))
    }
  }

  private lazy val client = server.serviceClient.implement[UserService]

and use it like:

client.create.handleRequestHeader(authenticate(userId)).invoke(user).map { response => ... }

It worked well with lagom 1.4.0. Please help me figure out with it.

1 Like

Hi Pavel,

have you tried falling back to the netty backend? (see https://www.lagomframework.com/documentation/1.4.x/scala/Migration14.html#Akka-HTTP-as-the-default-server-engine)

Also, could you share the Service.Descriptor and other details of your service API? You seem to be using some custom headers to handle authentication and I think that could be the cause of the issue.

Cheers,

Hi Ignasi,

Thank you for response.

I’ve been using “akka-http” backend all time by settings it in build.sbt: lagomServiceGatewayImpl in ThisBuild := "akka-http"

Here is my service descriptor:

override final def descriptor: Descriptor = {
    import Service._
    // @formatter:off
    named("user")
      .withCalls(
        restCall(Method.POST, "/service/user/create", create _),
        restCall(Method.GET, "/service/user/:id", get _),
        restCall(Method.PUT, "/service/user/:id", update _),
        restCall(Method.DELETE, "/service/user/:id", delete _),
        restCall(Method.GET, "/service/users?offset&limit", getList _),
        restCall(Method.POST, "/service/user/authByCredentials", findByCredentials _),
        restCall(Method.POST, "/service/user/authByApiKeys", findByApiAuth _)
      ).withHeaderFilter(SecurityHeaderFilter.Composed)
      .withTopics(
        topic("user-UserEvent", this.userEvents)
          .addProperty(
            KafkaProperties.partitionKeyStrategy,
            PartitionKeyStrategy[UserEvent](_.id.toString)
          )
        )
      .withAutoAcl(true)
    // @formatter:on
  }

Here is the complete file with headers processing.

sealed trait UserPrincipal extends Principal {
  val userId: UUID
  override def getName: String = userId.toString
  override def implies(subject: Subject): Boolean = false
}

object UserPrincipal {
  case class ServicelessUserPrincipal(userId: UUID) extends UserPrincipal
  case class UserServicePrincipal(userId: UUID, servicePrincipal: ServicePrincipal) extends UserPrincipal with ServicePrincipal {
    override def serviceName: String = servicePrincipal.serviceName
  }

  def of(userId: UUID, principal: Option[Principal]): UserPrincipal = {
    principal match {
      case Some(servicePrincipal: ServicePrincipal) =>
        UserPrincipal.UserServicePrincipal(userId, servicePrincipal)
      case _ =>
        UserPrincipal.ServicelessUserPrincipal(userId)
    }
  }
}

object SecurityHeaderFilter extends HeaderFilter {
  private val AUTH_HEADER = "apiKey"

  override def transformClientRequest(request: RequestHeader): RequestHeader = {
    request.principal match {
      case Some(userPrincipal: UserPrincipal) => request.withHeader(AUTH_HEADER, userPrincipal.userId.toString)
      case _ => request
    }
  }

  override def transformServerRequest(request: RequestHeader): RequestHeader = {
    request.getHeader(AUTH_HEADER) match {
      case Some(userId) =>
        request.withPrincipal(UserPrincipal.of(UUID.fromString(userId), request.principal))
      case None => request
    }
  }

  override def transformServerResponse(response: ResponseHeader, request: RequestHeader): ResponseHeader = response

  override def transformClientResponse(response: ResponseHeader, request: RequestHeader): ResponseHeader = response

  lazy val Composed: HeaderFilter = HeaderFilter.composite(SecurityHeaderFilter, UserAgentHeaderFilter)
}


object SecurityService {
  val API_HEADER = "X-API-HEADER"
  val SECRET_HEADER = "X-SECRET-HEADER"

  def authenticated[Request, Response](serviceCall: UUID => ServerServiceCall[Request, Response]): ServerServiceCall[Request, Response] =
    ServerServiceCall.compose { requestHeader =>
      requestHeader.principal match {
        case Some(userPrincipal: UserPrincipal) => serviceCall(userPrincipal.userId)
        case _ => throw Forbidden("User not authenticated")
      }
    }
}

object ClientSecurity {

  /**
    * Authenticate a client request.
    */
  def authenticate(userId: UUID): RequestHeader => RequestHeader = { request =>
    request.withPrincipal(UserPrincipal.of(userId, request.principal))
  }
}

You were right.

It works with Netty server. Can you describe possible cause of it? I might fix it

Hi Pavel,

the fact that everything works is a bit weird given the error.

After reviewing the original error trace I noticed the method the JVM can’t locate is a method in the play.core.server.Server object. The method that’s being looked up is Server#getHandlerFor(RequestHeader,ApplicationProvider) : Either. That method was added in Play 2.6.x.

Lagom 1.3.x is built on top of Play 2.5 while Lagom 1.4.x is built on Play 2.6.

I think your code is still pulling some artifacts on the Play 2.5 family causing this runtime error.

Please review your transitive dependencies and make sure all your artifacts are for Lagom 1.4.x, Play 2.6.x and Akka 2.5.x.

Hope this helps,

Hi Ignasi,

I noticed that there were many dependency conflicts. I will resolve them.

Thank you for help

1 Like