Gatling Evaluate Expression Language - scala

Gatling parses Strings parameter values and turn them into functions that will compute a result based on the data stored into the Session when they will be evaluated.
Gatling Documentation
Is there any way to do this manually in an exec?
I have multiple request body templates which use EL attributes, the request sent will differ based on a feeder
The code I currently have is as such:
// Body itself contains attributes say ${uuid}
val body1 = Source.fromResource("body1.json")
val body2 = Source.fromResource("body2.json")
val scn: ScenarioBuilder = scenario("Dynamic body")
.feed(feeder)
.exec(session => {
if(session("requestType").as[String].equals("first"))
session.set("request", body1)
else
session.set("request", body2)
session
}
.exec(http("Http Call")
.post(url)
.body(StringBody("${request}"))
// This won't evaluate the nested attribute and body will remain ${uuid} instead of an actual UUID
)
I expect that there won't be a way to evaluate nested EL attributes, but is there a way to manually evaluate it using session variables? Something along the lines of
.exec(session => {
val evaluatedBody = merge(body1, session)
session("request", evaluatedBody)
session
})
I've seen ELCompiler being referenced in some other questions, but not sure where to import it from and how to use it with session values

You should use ElFileBody that takes a file path parameter, which can be a function.
val scn = scenario("Dynamic body")
.feed(feeder)
.exec(http("Http Call")
.post(url)
.body(
ElFileBody(session =>
session("requestType").as[String] match {
case "first" => "body1.json"
case _ => "body2.json"
}
)
)
)

Related

how to get value from list based on condition in scala

I have a following val headers: List[HttpHeader]:
val someList = List(Server: Apache-Coyote/1.1, Set-Cookie:
JSESSIONID=879sdf97f98s7f9a87dgssa7987; Path=/userApi/; HttpOnly)
Now I want to get 879sdf97f98s7f9a87dgssa7987 from the above list. So I did the following:
someList.iterator.filter(_.name.equals("Set-Cookie")).map { x => x.value }
But this doesn't gives that particular value from list.
import spray.http.HttpHeaders.`Set-Cookie`
val sessionId: Option[String] = someList.collectFirst {
case h: `Set-Cookie` if h.cookie.name == "JSESSIONID" => h.cookie.content
}
Now just handle Option[String] appropriately.
.collectFirst works like a combination of .filter, .map, and .find.
First, it filters headers by type Set-Cookie.
Second, because at this point HttpHeader is already casted to Set-Cookie, we can access its .cookie property and use it in our search predicate.
Third, we are asking to find a cookie with name JSESSIONID and get its value.
The only way I got it working is as follows (although I don't like using split so until we find any elegant way):
val sessionId = someList.filter(_.name.equals("Set-Cookie")).
map { x => x.value.split(";")(0).split("=")(1)}.headOption
println("sessionId: "+sessionId.get)

Random value generator for same variable in json file

Is there a way to generate random values inside the session for the same parameter.
Ex json file-
{
"age": "${age}"
},
{
"age": "${age}"
}
val ageFeeder = Iterator.continually(Map ("age" -> (0 + ThreadLocalRandom.current().nextInt(100 - 0) + 1).toString ()))
val scn = scenario("test")
.exec(feed(ageFeeder))
.exec(session => {
// code to read the file using ElFileBody which replaces ${age} with randomly generated age
})
I want to generate random values for the number of times we are calling ${age} in the file.
The feed instruction should not be in one exec I think.
Try:
.feed(ageFeeder)
.exec(session => { ... })
assuming you won't know until execution how many ${age} values you need to replace, you might be better not using a feeder.
instead, you could have a session variable that's a list of all possible numbers, then use the gatling EL to randomly index into that list.
so at the top of your file
val ages: Seq[Int] = (1 to 100).toSeq
then at the start of your scenario, persist this list into the session
exec(session => session.set("ages", ages))
and then in your file, rather than ${ages} you can use
"${ages.random()}"

Akka Stream use HttpResponse in Flow

I would like to utilize a simple Flow to gather some extra data from a http service and enhance my data object with the results. The following illustrates the Idea:
val httpClient = Http().superPool[User]()
val cityRequest = Flow[User].map { user=>
(HttpRequest(uri=Uri(config.getString("cityRequestEndpoint"))), User)
}
val cityResponse = Flow[(Try[HttpResponse], User)].map {
case (Failure(ex), user) => user
case (Success(resp), user) => {
// << What to do here to get the value >> //
val responseData = processResponseSomehowToGetAValue?
val enhancedUser = new EnhancedUser(user.data, responseData)
enhancedUser
}
}
val processEnhancedUser = Flow[EnhancedUser].map {
// e.g.: Asynchronously save user to a database
}
val useEnhancementGraph = userSource
.via(getRequest)
.via(httpClient)
.via(getResponse)
.via(processEnhancedUser)
.to(Sink.foreach(println))
I have a problem to understand the mechanics and difference between
the streaming nature and materialization / Futures inside the Flow.
Following ideas did not explain it to me:
http://doc.akka.io/docs/akka-http/current/scala/http/implications-of-streaming-http-entity.html
akka HttpResponse read body as String scala
How do i get the value from the response into the new user object,
so i can handle that object in the following steps.
Thanks for help.
Update:
I was evaluating the code with a remote akka http server answering to requests between immediately and 10 seconds using the code below for parsing.
This led to the effect that some "EnhancedUser" Instances showed up at the end, but the ones who took too long to answer were missing their values.
I added .async to the end of the cityResponse parser at some time and the result output took longer, but was correct.
What is the reason for that behaviour and how does it fit together with the accepted Answer?
val cityResponse = Flow[(Try[HttpResponse], User)].map {
case (Failure(ex), member) => member
case (Success(response), member) => {
Unmarshal(response.entity).to[String] onComplete {
case Success(s) => member.city = Some(s)
case Failure(ex) => member.city = None
}
}
member
}.async // <<-- This changed the behavior to be correct, why?
There are two different strategies you could use depending on the nature of the entity you are getting from "cityRequestEndpoint":
Stream Based
The typical way to handle this situation is to always assume that the entity coming from the source endpoint can contain N pieces of data, where N is not known in advance. This is usually the pattern to follow because it is the most generic and therefore "safest" in the real world.
The first step is to convert the HttpResponse coming from the endpoint into a Source of data:
val convertResponseToByteStrSource : (Try[HttpResponse], User) => Source[(Option[ByteString], User), _] =
(response, user) => response match {
case Failure(_) => Source single (None -> user)
case Success(r) => r.entity.dataBytes map (byteStr => Some(byteStr) -> user)
}
The above code is where we don't assume the size of N, r.entity.dataBytes could be a Source of 0 ByteString values, or potentially an infinite number values. But our logic doesn't care!
Now we need to combine the data coming from the Source. This is a good use case for Flow.flatMapConcat which takes a Flow of Sources and converts it into a Flow of values (similar to flatMap for Iterables):
val cityByteStrFlow : Flow[(Try[HttpResponse], User), (Option[ByteString], User), _] =
Flow[(Try[HttpResponse], User)] flatMapConcat convertResponseToByteStrSource
All that is left to do is convert the tuples of (ByteString, User) into EnhancedUser. Note: I am assuming below that User is a subclass of EnhancedUser which is inferred from the question logic:
val convertByteStringToUser : (Option[ByteString], User) => EnhancedUser =
(byteStr, user) =>
byteStr
.map(s => EnhancedUser(user.data, s))
.getOrElse(user)
val cityUserFlow : Flow[(Option[ByteString], User), EnhancedUser, _] =
Flow[(ByteString, User)] map convertByteStringToUser
These components can now be combined:
val useEnhancementGraph =
userSource
.via(cityRequest)
.via(httpClient)
.via(cityByteStrFlow)
.via(cityUserFlow)
.via(processEnhancedUser)
.to(Sink foreach println)
Future Based
We can use Futures to solve the problem, similar to the stack question you referenced in your original question. I don't recommend this approach for 2 reasons:
It assumes only 1 ByteString is coming from the endpoint. If the endpoint sends multiple values as ByteStrings then they all get concatenated together and you could get an error when creating EnhancedUser.
It places an artificial timeout on the materialization of the ByteString data, similar to Async.await (which should almost always be avoided).
To use the Future based approach the only big change to your original code is to use Flow.mapAsync instead of Flow.map to handle the fact that a Future is being created in the function:
val parallelism = 10
val timeout : FiniteDuration = ??? //you need to specify the timeout limit
val convertResponseToFutureByteStr : (Try[HttpResponse], User) => Future[EnhancedUser] =
_ match {
case (Failure(ex), user) =>
Future successful user
case (Success(resp), user) =>
resp
.entity
.toStrict(timeout)
.map(byteStr => new EnhancedUser(user.data, byteStr))
}
val cityResponse : Flow[(Try[HttpResponse], User), EnhancedUser, _] =
Flow[(Try[HttpResponse], User)].mapAsync(parallelism)(convertResponseToFutureByteStr)

Aliasing objects from expensive statements in Scala pattern match

I have an expensive case statement which needs to hit the database to determine a complete match. If there is a match, the result from the aforementioned call must be used to perform further operations:
def intent = {
case request # GET(Path(Seg(database :: Nil))) if recordsFrom(database) != Nil =>
renderOutput(recordsFrom(database))
case ...
}
I would like to call recordsFrom(database) only once. In the above example, it is called twice. It seems like I should be able to apply some alias to the statement?
Lawrence, from what I'm seeing you're using Unfiltered to handle a RESTful request but you've also combined a database lookup with that response filtering. I would advise you not to do that. Instead I'd arrange things as following:
val dbReqCommand = new DBRequestCommand(myDbConPool)
def intent ={
case req # GET(Path(Seq(database :: Nil))) => dbReqCommand(req, database)
}
Wherein you've encapsulated the db requests in an object that you could substitute out for testing purposes (think integration tests without a DB backend.) Within the request handler you might then put in the response:
Option(recordsFrom(database)) match{
case Some(value) => OK ~> renderOpupt(value)
case None => //an error response or Pass
}
That way you might have something along the lines of:
trait DBReqPlan{
def dbReqCommand: RequestCommand[String]
def intent ={
case req # GET(Path(Seq(database :: Nil))) => dbReqCommand(req, database)
}
}
which is easier to test against and work with.
What's wrong with:
def intent = {
case request # GET(Path(Seg(database :: Nil))) =>
val records = recordsFrom(database)
if(!records.isEmpty){
renderOutput(records)
} else {
...
}
case ...
You can move the body of the first case to a different function if you want to avoid having too many nested blocks.

Specs2 JSONMatchers: mapping over Array elements?

I'm using the Specs2 JSONMatcher to validate that a GET request is being correctly converted from its internal representation (there are some manipulations we do before generating the JSON). What I need to do is, make sure that an element in the JSON array matches the corresponding object from our repository.
What I've tried:
val response = response.entity.asString // Spray's way of getting the JSON response
repository.all.map { obj =>
resp must */ ("id" -> obj.id)
resp must */ ("state" -> generateState(obj)
}
The problem is that the */ matcher just finds that "state": "whatever" (assuming generateState returns "whatever") exists somewhere in the JSON document, not necessarily in the same one matched by the ID
I tried using the indices but the repository.all method doesn't always return the elements in the same order, so there's no way of matching by index.
What I'd like to do is, iterate over the elements of the JSON array and match each one separately. Say an /## operator (or something) that takes matchers for each element:
resp /## { elem =>
val id = elem("id")
val obj = repository.lookup(id)
elem /("state" -> generateState(obj))
}
Does anyone have a way to do this or something similar?
Probably the easiest thing to do for now (until a serious refactoring of JsonMatchers) is to do some parsing and recursively use a JsonMatchers in a Matcher[String]:
"""{'db' : { 'id' : '1', 'state' : 'ok_1'} }""" must /("db" -> stateIsOk)
// a string matcher for the json string in 'db'
def stateIsOk: Matcher[String] = { json: String =>
// you need to provide a way to access the 'id' field
// then you can go on using a json matcher for the state
findId(json) must beSome { id: String =>
val obj = repository.lookup(id)
json must /("state" -> generate(obj))
}
}
// I am using my own parse function here
def findId(json: String): Option[String] =
parse(json).flatMap { a =>
findDeep("id", a).collect { case JSONArray(List(v)) => v.toString }
}
// dummy system
def generate(id: String) = "ok_"+id
case object repository {
def lookup(id: String) = id
}
What I did in the end is use responseAs[JArray], JArray#arr and JObject#values to convert the JSON structures into Lists and Maps, and then used the standard List and Map matchers. Much more flexible.