Can I set cookies before returning an action in Play Framework 2? - scala

I know I can set cookies in Ok(...).withCookies(...) when returning an action. However I wonder if there is a way to set some cookies by manipulating the request object. So that I can set some cookies in my models and my controller just need to send them back.

I'm doing this only as exercise, and also to show that Play framework is very flexible and it doesn't limit you in any sense. I figured out how to do this purely from Play source code, it is very clean and easy to read. This is NOT the preferred way to work with cookies or indeed with HttpRequest object in Play. As Jatin suggested you should decode your cookies to proper models, pass those models to your services and then convert result of your services to play.api.mvc.Result, thus keeping your http and business logic layers separated.
Here's the code( you can see that Headers object is not intended to be used this way):
import play.api.http.HeaderNames.COOKIE
val cookies = Cookies(request.headers.get(COOKIE)).cookies
val myCookies = cookies + ("cookieName" -> Cookie("cookieName", "cookieValue"))
val headersMap = request.headers.toMap
val myHeaderMap = headersMap +
(COOKIE -> Seq(Cookies.encode(myCookies.values.toSeq)))
val myHeaders = new play.api.mvc.Headers {
val data:Seq[(String, Seq[String])] = myHeaderMap.toSeq
}
val modifiedRequest = request.copy(headers = myHeaders)

Related

How to generate multivalue map with random values and inject into request body using a feeder in Gatling

I'm writing a load test for an api and want to create a feeder which generates random values to inject into the body of the POST request. I initially tried copying the random email example from the documentation and adding additional fields to the generated map, but when that didn't work I went down to a single field, basically copying the documentation; however, even this doesn't work for some reason. There are a bunch of solutions on here that use this syntax as well, but something about the way I'm doing is causing the fields I try to inject into the body to be null when the request is made.
Current Code:
val userFeeder: Iterator[Map[String, Unit]] =
Iterator.continually(Map("userName" -> ("user_" + Random.alphanumeric.take(15).mkString)))
var scn: ScenarioBuilder = scenario("GENERATE USER")
.feed(userFeeder)
.exec(
http("CREATE USER")
.post(userBaseUrl)
.headers(userHeaders)
.body(StringBody("userName: ${userName}")))
setUp(
scn.inject(atOnceUsers(1))
)
Ideally I'd like to be able to expand the feeder to include multiple values, i.e.
val userFeeder: Iterator[Map[String, Unit]] =
Iterator.continually(Map("userName" -> ("user_" + Random.alphanumeric.take(15).mkString),
"userEmail" -> (Random.alphanumeric.take(15).mkString) + "#random.edu"),
"address" -> Random.alphanumeric.take(15).mkString)))
and so on, but I'm a little stumped as to why my current code doesn't even work, as it seems to follow the documentation example pretty faithfully. The values are always null in my requests despite trying a few different strategies.
Log output
body:StringChunksRequestBody{contentType='application/json', charset=UTF-8, content=userName: ()}
Figured it out. Turns out that even though Feeder is a wrapper for Iterator, the proper way to do what I want is to declare it like this:
val userFeeder: Feeder[Any] =
Iterator.continually(Map("userName" -> ("user_" + Random.alphanumeric.take(15).mkString),
"userEmail" -> (Random.alphanumeric.take(15).mkString) + "#random.edu"),
"address" -> Random.alphanumeric.take(15).mkString)))

Stub a Http4s 0.20.x Client for testing

I'd like to test that a Http4s Client is being called from my class ClassUnderTest (to make a HTTP request) and that the request made contains the headers I expect.
In 0.18.x, I did something like below. Using a side effect to store the value of the headers so that I can make an assertion after the call. In the example below, f.execute(...) is expected to make a PUT with the client instance and I'm trying to record all request handling and storing the headers.
"Request has headers" >> {
var headers = List[String]()
val client = Client[IO](new Kleisli[IO, Request[IO], DisposableResponse[IO]](request => {
headers = request.headers.map(_.name.toString()).toList
Ok().map(DisposableResponse(_, IO.pure(())))
}), IO.pure(()))
val f = ClassUnderTest(client)
f.execute("example")
headers must_== List(
"Content-Type",
"X-Forwarded-For",
"Content-Length"
)
}
The real code is here if you're interested.
ClassUnderTest took a Client[IO] so I could get the above working.
class ClassUnderTest(client: http4s.Client[IO])
In Http4s 0.20.12, I had to change the signature to:
class ClassUnderTest(client: Resource[IO, http4s.Client[IO]])
...and now I can't figure out how to stub out the client for tests. I experimented with JavaNetClientBuilder but that doesn't help because I can get an instance of Client (after .create) and now I need a Resource[IO, http4s.Client[IO]].
How can I use a test double to stand in for the Client / Resource[F, Client[F]] so that I can test the requests it makes?
The testing page on the docs doesn't really help me. I want a test double, not to test all the functionality of the Service (I don't want to startup a server).

How to use a single OAuth2.0 token for Multiple Virtual Users in a Gatling load test

I need to load test an API that requires an OAuth2.0 token via Gatling (of which I'm a complete novice!) but would like each virtual user to use the same token. I'm retrieving the token ok (I think) and putting it in a variable called 'access' but I keep getting 'no attribute named 'access' is defined' when the test itself starts.
My token retrieval looks like the following(along with httpConf, used below):
class MySimulation extends Simulation {
val httpConf = http
.baseUrl("https://MyBaseUrl.Com/")
.acceptHeader("application/json")
.doNotTrackHeader("1")
.acceptLanguageHeader("en-UK,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
.shareConnections
val header = Map("Content-Type" -> """application/x-www-form-urlencoded""")
al auth = scenario("Retrieve Token")
.exec(http("POST OAuth Req")
.post("https://SomeTokenUrl")
.formParam("resource", "someresource")
.formParam("grant_type", "somegranttype")
.formParam("client_secret", "someclientsecret")
.formParam("client_id", "someclientid")
.headers(header).check(status.is(200)).check(jsonPath("$.access_token").find.saveAs("access")))
I then tried setting up the load test as (Note: I did initially put 'Map', rather than the mutable variant, but read somewhere the default was immutable, and wondered if this was why the header couldn't update):
val headers_10 = scala.collection.mutable.Map("Content-Type" -> "application/json; charset=ISO-8859-1", "Authorization" -> "Bearer ${access}")
val scn = scenario("MyService Gatling test run")
.exec(http("")
.post("Myservice/api")
.headers(headers_10.toMap)
.body(StringBody("""{"SomeProperty": "Some Value"}"""))
.asJson
.check(status.is(200)))
setUp(
auth.inject(constantUsersPerSec(1) during (2 seconds)),
scn.inject(nothingFor(2 seconds),
constantUsersPerSec(10) during (10 seconds)
).protocols(httpConf))
.assertions(global.responseTime.max.lt(500))
.assertions(forAll.failedRequests.percent.lte(1))
.assertions(global.responseTime.mean.lte(100))
The idea was that the token retrieval would complete prior to the load test kicking in and the 'access' variable would then be used by the load test scenario, but it gives the following result:
ERROR : Failed to build request: No attribute named 'access' is defined
I've reached the end of my tether with it. I'm guessing it might be something to do with scopes, and perhaps the variable doesn't carry over to the load test scenario, but I've seen examples elsewhere that seem to recommend exactly that set up, so I don't know whether these other examples are partially complete or what.
Today I implemented this scenario for my project. Please see the code below and it will work for you as well.
Note: I have written this code with my API's required params. You can modify this code as per your requirement. For now, this code is written in a single class. I have implemented this code in a proper format as well with the use of different classes and property files. I will post that one as well.
For a single class, the code goes as follows:
`
package aapi
import io.gatling.core.Predef._
import io.gatling.http.Predef._
class manifestSimulation extends Simulation {
private var token = ""
object authAvi{
// This is the request(API) which we are going to use for generating the auth token 1 time and then will feed this token into subsequent request.
var postBody = "{\"username\":\"devusername\”,\”password\”:\”devpassword”}”
val auth = scenario("Retrieve our auth Token which will be used in the subsequent request“)
.exec(
http("POST OAuth Req")
.post(“User Post URL“)
.body(StringBody(postBody))
.header("ClientId", “test”)
.header("DSN", “devDB”)
.header("accept", "application/json")
.header("Accept-Language", "en-us")
.header("Content-Type", "application/json")
.check(status.is(200))
.check(jsonPath("$.token")
.saveAs("token")))
.exitHereIfFailed
.exec{session => { token = session("token").as[String]
session}}
}
object manifest {
// This is the request(API) which we are going to hit multiple times using the token which we generated from the previous auth API
var manifestHeaders = Map("ClientId" -> “test”, "DSN" -> "devDB", "Token" -> "${token}")
val manifestMethod = exec(session => session.set("token", token))
.exec(http("Manifest Details")
.get(“Your get URL“)
.headers(manifestHeaders)
.check(status.is(200))
)
}
val scn = scenario(“**********This is your actual load test*******************”)
.exec(manifest.manifestMethod)
setUp(
authAvi.auth.inject(constantUsersPerSec(1) during (1 seconds)), // fire 1 requests per second for 1 second to retrieve token
scn.inject(nothingFor(4 seconds), // waits 4 seconds as a margin to process token and this time varies for every user
constantUsersPerSec(5) during (5 seconds))) // fire 5 requests per second for 5 seconds which will result in 25 (5*5) requests and overall 26 requests when the report gets generated (because we have 1 request for auth token and 25 requests of our intended API (25+1 = 26)
}`
Sessions are per user and no session data is shared between users. So while you have 1 user running your 'auth' scenario and saving the token, it is two different users that run 'scn' and they don't have access to the session values of the auth user.
It's not recommended practice, but you can solve this by pushing the auth token into a regular scala var and the setting this in the auth scenario and reading it in the main scenario - you just need to be sure that auth always completes before you inject any other users.
var token: String = ""
then in the auth scenario, have a step at the end such as
.exec(session => {
token = session("access").as[String]
session
})
then at the start of the scn scenario have a step to set the session variable
.exec(session.set("access", token))
I've used this pattern in the past and it works, but I'm sure there are nicer ways to do it
#Tarun,
When I did it, I had the 'exec' in my scenario, rather than the set up, and used the following syntax:
val dataToUse = feed(testData)
.exec(session => session.set("access", token))
.exec(http("")
.post("*the_URL_to_send_to)*")
.headers(headers_10.toMap)
.body(RawFileBody("${filePath}")).asJson
.check(status.is(200))
)
As mentioned in the comments in the previous discussion, this was because I was using a later version of gatling and the 'get' method was no longer part of the session api.
My full solution was as follows - note that are a number of things to look out for that might not apply to your solution. I used an object, as it just made things clearer in my mind for what I was trying to do! Also, some of the imports are probably redundant, as I included them as part of scattergun approach to finding something that worked!
Finally, I basically list the contents of a directory, and cycle through the files listed in it, using each one as a feeder. You look as if you're using a literal template of json, so probably don't need that, but I thought I would include it for completeness, as it's quite handy - if you change the format of your json, you don't need to mess around changing the template in the simulation, you just clear the directory and drop examples of the new format in there and away you go! :
package myTest
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
import scala.collection.JavaConversions._
import java.io.File
import java.io.FileNotFoundException
class myTestSimulation extends Simulation {
val httpConf = http
.baseUrl("*your_base_URL*")
.acceptHeader("application/json") // Here are the common headers
.doNotTrackHeader("1")
.acceptLanguageHeader("en-UK,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
.shareConnections
val header = Map("Content-Type" -> """application/x-www-form-urlencoded""");
private var token = ""
val auth = scenario("Retrieve Token")
.exec(
http("POST OAuth Req")
.post("*URL_for_Token*")
.formParam("resource", "*your_resource_value*")
.formParam("grant_type", "*your_grant_type*")
.formParam("client_secret", "*your_client_secret_value*")
.formParam("client_id", "*your_client_id_value*")
.headers(header)
.check(status.is(200)).check(jsonPath("$.access_token").find.saveAs("access")))
.exec{session => { token = session("access").as[String]
session}}
object myTestObject {
var headers_10 = scala.collection.mutable.Map("Content-Type" -> "application/json; charset=ISO-8859-1", "Authorization" -> "Bearer ${access}")
val testData = Iterator.continually(
new File("*pathway_to_file*") match {
case d if d.isDirectory => d.listFiles.map(f => Map("filePath" -> f.getPath))
case _ => throw new FileNotFoundException("Samples path must point to directory")
}).flatten
val myTestObjectMethod = feed(testData)
.exec(session => session.set("access", token))
.exec(http("")
.post("*the_URL_to_send_to(don't_forget_that_base_URL_above_is_automatically_stuck_to_the_front_of_this!)*")
.headers(headers_10.toMap)
.body(RawFileBody("${filePath}")).asJson
.check(status.is(200))
)
}
val scn = scenario("my_actual_load_test")
.exec(myTestSimulation.myTestObject)
setUp(
auth.inject(constantUsersPerSec(1) during (1 seconds)), // fire 1 requests per second for 1 second to retrieve token
scn.inject(nothingFor(2 seconds), // waits 2 seconds as a margin to process token
constantUsersPerSec(50) during (300 seconds) // fire 50 requests per second for 300 seconds
).protocols(httpConf))
.assertions(global.responseTime.max.lt(500)) // set max acceptable response time
.assertions(forAll.failedRequests.percent.lte(1)) // less than 1% of tests should fail
.assertions(global.responseTime.mean.lte(100)) // set average response time
}
I mean, I've probably made a typo somewhere along the line as I removed the sensitive stuff from it, but hopefully that will do what you need.

Setting Cookies using Http4s Client

I am using Http4s library to make HTTP calls to a REST web service. the rest web service requires me to set an authentication cookie.
I have written the following code to set this cookie.
val client = PooledHttp1Client()
val uri = Uri.uri("http://localhost/restService")
val req = Request(GET, uri)
req.headers.put(`Content-Type`(MediaType.`application/json`))
val cookie = org.http4s.Cookie("foo_session", getLoginSessionId, domain = Some("localhost"), path=Some("/"))
req.headers.put(org.http4s.headers.Cookie(cookie))
val task = client.expect[Response](req)
val list = task.run
list.response.foreach(println)
client.shutdownNow()
When I run this code I get a 401 error, meaning that the web service does not recognize that the cookie was set.
Now If I write the same code using apache http client. then everything works fine. The code below is doing exactly the same thing as above.
val get = new HttpGet(s"http://localhost/restService")
get.setHeader("Content-type", "application/json")
val client = new DefaultHttpClient()
val respHandler = new BasicResponseHandler
val cookieStore = new BasicCookieStore()
val cookie1 = new BasicClientCookie("foo_session", getLoginSessionId)
cookie1.setDomain("localhost")
cookie1.setPath("/")
cookieStore.addCookie(cookie1)
val localContext = new BasicHttpContext()
localContext.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore)
localContext
val responseString = client.execute(get, respHandler, cookieContext)
val list = parse(responseString).extract[Response]
list.response.foreach(println)
list.response
It seems that your cookie is not used into response.
Did you try to use the following approach :
val cookie = org.http4s.ResponseCookie("token", su.token.getOrElse("No token"), httpOnly = true, secure = true)
Ok("resp").map(_.addCookie(cookie))
if you want to attach the cookie server sent to your client, you can try Cookie Jar. https://github.com/http4s/http4s/blob/main/client/src/main/scala/org/http4s/client/middleware/CookieJar.scala
Http4s strictly check the cookie from server so chance are that some cookie entries are not replied. if it is the case, you may try this one:
https://github.com/chenharryhua/nanjin/blob/master/http/src/main/scala/com/github/chenharryhua/nanjin/http/client/middleware/package.scala#L39

PlayFramework Testing: Uploading File in Fake Request Errors

I want to test my method, which requires uploading a file. It is initialized like this:
val tempFile = TemporaryFile(new java.io.File("/home/ophelia/Desktop/students"))
val part = FilePart[TemporaryFile](
key = "students",
filename = "students",
contentType = Some("text/plain"),
ref = tempFile)
val files = Seq[FilePart[TemporaryFile]](part)
val formData = MultipartFormData(
dataParts = Map(),
files = Seq(part),
badParts = Seq(),
missingFileParts = Seq())
I pass it into the FakeRequest:
val result = route(
FakeRequest(POST, "/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session " + testSessionA.id.string))
.withMultipartFormDataBody(formData)
)
But when I run the test I get the following error:
Cannot write an instance of play.api.mvc.AnyContentAsMultipartFormData to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsMultipartFormData]
What am I doing wrong and how to fix it? I looked on the internet, I didnt find any useful way to understand and resolve this problem.
It's important to remember that http requests are entirely text. route() takes an implicit Writeable to convert the body type of the provided request into text. Without the right Writeable, there is no way to know how to turn MultipartFormData into a request body.
There doesn't seem to be a Writeable for MultipartFormData, but you can provide your own. jroper has a great Writeable you could use for reference. (EDIT: That code is buggy, here's a working Writeable for AnyContentAsMultipartFormData)
Once you have your Writeable, you will need to make it accessible to your call to route(). Bear in mind, you currently have a FakeRequest[AnyContentAsMultipartFormData], not a FakeRequest[MultipartFormData]. You can either convert your request first:
val request = FakeRequest(POST,
"/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session "))
.withMultipartFormDataBody(formData)
route(request.map(_.mdf).asInstanceOf[FakeRequest[MultipartFormData[TemporaryFile]]])
or make your Writeable a Writeable[AnyContentAsMultipartFormData].
route for a given Request[T] requires an implicit parameter of type Writeable[T] that knows how to serialize the request body, because it will actually call the controller action just like an actual web request would, by pushing bytes onto it.
The problem is that there is no Writeable[MultipartFormData] predefined (you can see which are in play.api.test.Writeables).
This means you basically have two options:
write your own Writeable that serializes a MultipartFormData into bytes
Skip the routing part and call the action directly instead, like in the accepted answer in Play Framework Testing using MultipartFormData in a FakeRequest. This way of testing actions takes a shortcut and does not actually serialize and deserialize the request.
IMHO the first option is way too much pain for the gain, but if you go down that road, maybe contribute it to play when you succeed.
One of the possible solutions is to use wsUrl. For example
"File uploading action" should {
"upload sent file and result in ID" in {
val file = Paths.get(getClass.getResource("/1.txt").toURI)
val action = wsUrl("/upload").post(Source.single(FilePart("file", "hello.txt", Option("text/plain"), FileIO.fromPath(file))))
val res = Await.result(action, timeout)
res.status mustBe OK
res.body contains "123"
}
}