Adding headers to WSRequest in WS client from Play Framework - scala

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)

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.

missing FromRequestUnmarshaller[Entity] on akka post route

Let me start by saying that i am very new to akka-http, none of the books i have covered the marsheling topic well. So it is bit of a blackbox for me. I was able to obtain the following (Un)Marsheller which is capable of returning both json and protobuf based on a request header.
This part of the code works fine and i have a get route defined in akka-http and it works fine.
trait PBMarshaller {
private val protobufContentType = ContentType(MediaType.applicationBinary("octet-stream", Compressible, "proto"))
private val applicationJsonContentType = ContentTypes.`application/json`
implicit def PBFromRequestUnmarshaller[T <: GeneratedMessage with Message[T]](companion: GeneratedMessageCompanion[T]): FromEntityUnmarshaller[T] = {
Unmarshaller.withMaterializer[HttpEntity, T](_ => implicit mat => {
case entity#HttpEntity.Strict(`applicationJsonContentType`, data) =>
val charBuffer = Unmarshaller.bestUnmarshallingCharsetFor(entity)
FastFuture.successful(JsonFormat.fromJsonString(data.decodeString(charBuffer.nioCharset().name()))(companion))
case entity#HttpEntity.Strict(`protobufContentType`, data) =>
FastFuture.successful(companion.parseFrom(CodedInputStream.newInstance(data.asByteBuffer)))
case entity =>
Future.failed(UnsupportedContentTypeException(applicationJsonContentType, protobufContentType))
})
}
implicit def PBToEntityMarshaller[T <: GeneratedMessage]: ToEntityMarshaller[T] = {
def jsonMarshaller(): ToEntityMarshaller[T] = {
val contentType = applicationJsonContentType
Marshaller.withFixedContentType(contentType) { value =>
HttpEntity(contentType, JsonFormat.toJsonString(value))
}
}
def protobufMarshaller(): ToEntityMarshaller[T] = {
Marshaller.withFixedContentType(protobufContentType) { value =>
HttpEntity(protobufContentType, value.toByteArray)
}
}
Marshaller.oneOf(protobufMarshaller(), jsonMarshaller())
}
}
the issue i am facing is on the post route.
(post & entity(as[PropertyEntity])) { propertyEntity =>
complete {
saveProperty(propertyEntity)
}
}
During compilation time, i get the following error
Error:(20, 24) could not find implicit value for parameter um: akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller[PropertyEntity]
(post & entity(as[PropertyEntity])) { propertyEntity =>
I am not sure exactly what i am missing. Do i need to define an implicit FromRequestUnmarshaller ? if so what should it have?
i was able to hack something together that works for the moment, but i still don't know how to create a general Unmarshaller that can decode any ScalaPB case class
implicit val um:Unmarshaller[HttpEntity, PropertyEntity] = {
Unmarshaller.byteStringUnmarshaller.mapWithCharset { (data, charset) =>
val charBuffer = Unmarshaller.bestUnmarshallingCharsetFor(data)
JsonFormat.fromJsonString(data.decodeString(charBuffer.nioCharset().name()))(PropertyEntity)
/*PropertyEntity.parseFrom(CodedInputStream.newInstance(data.asByteBuffer))*/
}
}
even this i don't know how to have both decoders enabled at the same time. so i have commented one out.

spray.io debugging directives - converting rejection to StatusCodes

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.

Attach a callback to run after a scala spray server successfully sends a response

I want to do something like the following:
object SprayTest extends App with SimpleRoutingApp {
implicit val system = ActorSystem("my-system")
import system.dispatcher
startServer(interface = "0.0.0.0", port = 8080) {
post {
path("configNetwork") {
entity(as[Config]) { config =>
complete {
// has a response indicating "OK"
// also, restarts the network interface
handleConfig(config)
}
}
}
}
}
}
The problem is that handleConfig reinitializes the network interface, so remote hosts accessing this endpoint never receive their response.
One way to solve this is to run handleConfig in a separate thread and complete the request immediately with some response like "OK". This isn't a good solution however because it introduces a race condition between the future and the request completion (also, it always fails if the future is executed in a "same thread" execution context).
Therefore, an ideal solution would be to attach a callback to a "write response" future and perform the network re-initialization there, after the response has been successfully sent. Is there a way to achieve this in the spray framework?
As a simple example of the race condition, consider the following two examples:
object SprayTest extends App with SimpleRoutingApp {
implicit val system = ActorSystem("my-system")
import system.dispatcher
startServer(interface = "0.0.0.0", port = 8080) {
post {
path("configNetwork") {
entity(as[Config]) { config =>
ctx =>
ctx.complete("OK")
System.exit(0) // empty response due to this executing before response is sent
}
}
}
}
}
object SprayTest extends App with SimpleRoutingApp {
implicit val system = ActorSystem("my-system")
import system.dispatcher
startServer(interface = "0.0.0.0", port = 8080) {
post {
path("configNetwork") {
entity(as[Config]) { config =>
ctx =>
ctx.complete("OK")
Thread.sleep(1000)
System.exit(0) // response is "OK" because of the sleep
}
}
}
}
}
You can use the withAck method on HttpResponse to receive notification when the response is sent on the wire. Here is a sketch of what that would look like in code, though I suspect if you're reconfiguring the low-level network interface then you will need to actually close the http listener and rebind.
case object NetworkReady
class ApiManager extends HttpServiceActor with Directives {
override def receive: Receive = networkReady
private def networkReady: Receive = runRoute(routes) orElse networkManagementEvents
private def networkManagementEvents: Receive = {
case Config =>
context.become(reconfiguringNetwork)
magicallyReconfigureNetwork pipeTo self
}
private def magicallyReconfigureNetwork: Future[NetworkReady] = ???
private def reconfiguringNetwork: Receive = {
case NetworkReady => context.become(networkReady)
case _: HttpRequest => sender() ! HttpResponse(ServiceUnavailable)
case _: Tcp.Connected => sender() ! Tcp.Close
}
private def routes: Route = {
(post & path("configNetwork") & entity(as[Config])) { config =>
complete(HttpResponse(OK).withAck(config))
}
}
}