Is Play 2.7's WsClient supposed to be stateless?


(Herbie Porter) #1

We’re in the process of upgrading from Play 2.6 to Play 2.7 and have run into a small issue. Some of our functional tests spin up our application and hit the endpoints using WsClient. These tests were all passing in Play 2.6 but some of them fail now we’re on Play 2.7.

After doing some digging it appears that the WsClient in Play 2.7 isn’t stateless and
actually preserves the cookie between requests. II’m not sure if this was an intended feature or a side effect of some other change but I couldn’t see it documented anywhere?

I’ve created a very small sample app which demonstrates this:

routes

GET         /echo-session    wsclient.EchoSessionController.echoSession

EchoSessionController.scala

package wsclient

import javax.inject.{Inject, Singleton}
import play.api.libs.json.Json
import play.api.mvc._
import play.api.{Logger, LoggerLike}

import scala.concurrent.ExecutionContext

@Singleton
class EchoSessionController @Inject()()
  (cc: ControllerComponents)(implicit executionContext: ExecutionContext) extends AbstractController(cc) {

  protected val logger: LoggerLike = Logger(this.getClass)

  val echoSession: Action[AnyContent] = Action { implicit request: Request[AnyContent] =>
    if (request.session.get("session-exists").isDefined) {
      logger.warn(s"Session exists. [$request] [${request.headers.get("Cookie")}]")
      Ok(Json.toJson(request.session.data))
    } else {
      logger.warn(s"Session does not exist. [$request] [${request.headers.get("Cookie")}]")
      Ok(Json.toJson(request.session.data)).addingToSession("session-exists" -> "true")
    }
  }
}

WsClientISpec.scala

package wsclient

import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.time.{Millis, Seconds, Span}
import org.scalatest.{Matchers, WordSpec}
import org.scalatestplus.play.guice.GuiceOneServerPerSuite
import play.api.libs.ws.ahc.AhcWSClient

class WsClientISpec extends WordSpec with Matchers with ScalaFutures with GuiceOneServerPerSuite {

  implicit val system: ActorSystem = ActorSystem("system")
  implicit val materializer: ActorMaterializer = ActorMaterializer()

  override implicit val patienceConfig: PatienceConfig =
    PatienceConfig(timeout = Span(10, Seconds), interval = Span(50, Millis))

  "AhcWsClient" should {
    "be stateless if we share an instance" in {
      val wsClient: AhcWSClient = AhcWSClient()

      val result_1 = wsClient.url("http://localhost:" + portNumber.value + "/echo-session").get().futureValue

      result_1.body shouldBe "{}"

      val result_2 = wsClient.url("http://localhost:" + portNumber.value + "/echo-session").get().futureValue

      // Play 2.6 = Pass
      // Play 2.7 = Fail [Actual   :"{["session-exists":"true"]}"] - For some reason the second call uses the cookie which is returned from the 1st call so restores the session.
      result_2.body shouldBe "{}"

      wsClient.close()
    }
  }

  "AhcWsClient" should {
    "be stateless if we create a fresh instance each time" in {
      val wsClient_1: AhcWSClient = AhcWSClient()
      val wsClient_2: AhcWSClient = AhcWSClient()

      val result_1 = wsClient_1.url("http://localhost:" + portNumber.value + "/echo-session").get().futureValue

      result_1.body shouldBe "{}"

      val result_2 = wsClient_2.url("http://localhost:" + portNumber.value + "/echo-session").get().futureValue

      // Play 2.6 = Pass
      // Play 2.7 = Pass
      result_2.body shouldBe "{}"

      wsClient_1.close()
      wsClient_2.close()
    }
  }
}

(Slisaasquatch) #2

I had the exact same issue. I simply swapped WSClient for HttpURLConnection in my test.


(Marcos Pereira) #3

It should not send the cookies unless you ask for it for that specific request.

Can you create a full project that reproduces the problem?

Best.


(Herbie Porter) #4

Sure thing - https://github.com/HerbiePorter/play-wsclient-test


(Marcos Pereira) #5

Hey @HerbiePorter,

This fixes the problem: https://github.com/playframework/play-ws/pull/307.

It will be part of the next release of play-ws.