简体   繁体   中英

Lagom service does not responding when it receives Source

I'm playing with Lagom and created service receiving Source as input and returning case class object:

import akka.NotUsed
import akka.stream.scaladsl.Source
import com.lightbend.lagom.scaladsl.api.{Service, ServiceCall}
import play.api.libs.json.{Format, Json}

trait TestService extends Service {
  def test(): ServiceCall[Source[String, NotUsed], ResultData]

  override final def descriptor = {
    import Service._
    named("DocsStore")
      .withCalls(
        call(test())
      )
  }
}


case class ResultData(uploadId: String, length: Long)

object ResultData {
  implicit val format: Format[ResultData] = Json.format[ResultData]
}

Service implementation is:

class TestServiceImpl()(
  implicit val materializer: Materializer,
  implicit val ec: ExecutionContext
) extends TestService {
  val logger = Logger(getClass.getName)

  override def test(): ServiceCall[Source[String, NotUsed], ResultData] = ServiceCall{ source=>
    source.runForeach(s=>logger.info(s"String $s")).map(_=>ResultData("TestResult", 12))
  }
}

When I call this service from Play application's controller:

  def test = Action.async { req=>
    testService.test().invoke(Source("A"::"B"::"C"::Nil)).map(rd=>Ok(Json.toJson(rd)))
  }

"runForeach" on service side successfully prints A, B, C but service itself does not return any result value (ResultData("TestResult", 12) is expected) causing Play application throw exception:

play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[DeserializationException: No content to map due to end-of-input
 at [Source: akka.util.ByteIterator$ByteArrayIterator$$anon$1@309c63af; line: 1, column: 0]]]
    at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:293)
    at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:220)
    at play.api.GlobalSettings$class.onError(GlobalSettings.scala:160)
    at play.api.DefaultGlobal$.onError(GlobalSettings.scala:188)
    at play.api.http.GlobalSettingsHttpErrorHandler.onServerError(HttpErrorHandler.scala:100)
    at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:100)
    at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:99)
    at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:346)
    at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:345)
    at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)

How this could be fixed?

This happens because Lagom interprets the completion of the stream as a signal to close the connection. The connection is closed before the response can be sent.

This has been raised as an issue in GitHub: https://github.com/lagom/lagom/issues/814

A possible workaround is to leave the stream open until the response is received, as demonstrated in the documentation on testing streaming services :

// Use a source that never terminates (concat Source.maybe) so we
// don't close the upstream, which would close the downstream
val input = Source("A"::"B"::"C"::Nil).concat(Source.maybe)

However, if using this strategy, the service implementation will also need to be changed, as the implementation in the question above only sends the response when the stream is completed. Instead, you'll need to design into your protocol an explicit completion message that signals to the service to send the response.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM