Gatling: token service and structure of simulation

My scanario consists of calling a security-service with a user name and password and getting a token in return.
Next I want to use this token to make a http request With this token as a header.

But how could I set this up as a scenario? I Guess I have to Call the token service and then save the token in a variable and reuse it in my request.

Probably this is a common situation, and does anybody have an example on how to do this?

I was thinking like this:

`
class LoginSimulation extends Simulation {
//call the token server with username and password
//save the token as a variable to be used in the call below, extract the token from the response
//the token is to be sent as a header in the call below (as X-Token below)

val httpConf = http
.baseURL(https://coop.ua.com)
.acceptEncodingHeader(""“gzip,deflate”"")
.baseHeaders(Map(“X-Token” → “1234asdf”))

val scn = scenario(“Scenario”)
.exec(
http(“getProfile”)
.get("/user/profile")
.check(status.is(200))

setUp(scn.inject(constantUsersPerSec(5) during (10))).protocols(httpConf)
}
`
But do I call the token server outside/before the line “val httpConf = http” or do I call it as a first call? If so how do I add it as a baseHeader?

Cheers,

Magnus

Do I get it right?

  • the token server replies with the token in the body, not a cookie?

  • the client sends the token back as a specific header named X-Token, not a cookie?$
    Just asking because we already had some misunderstand here on headers and cookies.

If there is no other way, can I add:

.baseHeaders(Map("X-Token" -> "1234asdf"))
for each .exec in my chain?
I hope there is a better way in defining it “globally” in my httpConf as a base header.

Yes, you are right, the token gets returned in the body or maybe in the response header (the auth service is under development), but this is not a cookie I am sure.

Magnus

So the client is some native app, not a browser as afaik, you can’t craft headers in javascript.

You can’t set such a header in the HttpProtocol, as it’s not global: it doesn’t exist until you’re logged in.
You have to set it per request: http://gatling.io/docs/2.0.0/http/http_request.html#http-headers

As you’ll have to pass it to almost every request, you should probably create a factory method for creating a request with this header, like here: http://gatling.io/docs/2.0.0/cookbook/handling_jsf.html.
If you have some Scala skills, you can also consider some implicit method, search for John Arrowwood’s DSL examples in the mailing list archive.

Cheers,

Stéphane

This is just plain http REST WS calls from an (iPhone/Android) App, yes.
My loadtests is supposed to test through a rest-api. That is it.

`
class LoginSimulation extends Simulation {

val httpConf = http
.baseURL(https://low.se)
.acceptEncodingHeader(""“gzip,deflate”"")

val scn = scenario(“Scenario”)
.exec(
http(“getProfile”)
.get("/user/profile")
.check(regex("""""").saveAs(“token”))
.exec(
http(“getCSomething”)
.get("/my/stuff")
.param(“token”, “${token}”)
.
.
.
.

`

`

`

Magnus,

Maybe this will help: I started by creating a generic REST interface for the common elements of a REST call, like so:

`
object RESTful {
object GET {
def apply( desc: String, url: String ) =
http( desc )
.get( url )
.headers( Headers.get )
}
object POST {
def apply( desc: String, url: String, body: String ) =
http( desc )
.post( url )
.headers( Headers.post )
.body( StringBody( body ) )
}
object PUT {
def apply( desc: String, url: String, body: String ) =
http( desc )
.put( url )
.headers( Headers.post )
.body( StringBody( body ) )
}
object DELETE {
def apply( desc: String, url: String ) =
http( desc )
.delete( url )
.headers( Headers.get )
}
}

`

The headers are defined elsewhere, and look like this:

`
object Headers {
val KEEP_ALIVE = Map( “Keep-Alive” → “115” )
val XHR = Map( “X-Requested-With” → “XMLHttpRequest” )
val SENDING_FORM_DATA = Map( “Content-Type” → “application/x-www-form-urlencoded” )
val SENDING_JSON = Map( “Content-Type” → “application/json;charset=UTF-8” )
val EXPECTING_JSON_OR_TEXT = Map( “Accept” → “application/json, text/plain, /” )

val get = KEEP_ALIVE
val post = KEEP_ALIVE ++ SENDING_JSON
val formPost = KEEP_ALIVE ++ SENDING_FORM_DATA
val ajax = KEEP_ALIVE ++ XHR ++ EXPECTING_JSON_OR_TEXT
val ajaxSend = KEEP_ALIVE ++ XHR ++ SENDING_JSON ++ EXPECTING_JSON_OR_TEXT
}
`

Then I created an application-specific wrapper for the RESTful object, which adds application-specific headers, like so:

`
object RTDE {
object GET {
def apply( desc: String, url: String ) =
RESTful.GET( desc, url )
.queryParam( “access_token”, ACCESS_TOKEN.value )
}
object POST {
def apply( desc: String, url: String, body: String ) =
RESTful.POST( desc, url, body )
.queryParam( “access_token”, ACCESS_TOKEN.value )
}
object PUT {
def apply( desc: String, url: String, body: String ) =
RESTful.PUT( desc, url, body )
.queryParam( “access_token”, ACCESS_TOKEN.value )
}
object DELETE {
def apply( desc: String, url: String ) =
RESTful.DELETE( desc, url )
.queryParam( “access_token”, ACCESS_TOKEN.value )
}
}

`

This is a work-in-progress… this is going to be updated with some additional application-specific logic in the next few days.

Then for every service/method, I create a library that lets me call a specific service as a one-liner. An example of that library looks like this:

`
object Terminology {

val path = Config.Endpoint.Services.url + “/terminology”

def getByPath(
desc: Option[String] = None,
path: Option[String] = None,
element: Option[String] = None,
inref: Option[String] = None,
outref: Option[String] = None
) = {
var p: Map[String,Any] = Map()
element.map { x => p += ( “element” → x )}
inref.map { x => p += ( “inref” → x )}
outref.map { x => p += ( “outref” → x )}

RTDE.GET(
desc = desc.getOrElse(“Terminology.getByPath”),
url = path + path.getOrElse("/cigna")
)

}

def create(
desc: Option[String] = None,
term: Option[String] = None
) =
RTDE.POST(
desc = desc.getOrElse(“Terminology.create”),
url = path,
body = term.getOrElse( TERMINOLOGY_DEFINITION.value )
)

def update(
desc: Option[String] = None,
path: Option[String] = None,
term: Option[String] = None
) =
RTDE.PUT(
desc = desc.getOrElse(“Terminology.update”),
url = Terminology.path + path.getOrElse( TERMINOLOGY_PATH.value ),
body = term.getOrElse( TERMINOLOGY_DEFINITION.value )
)

def delete(
desc: Option[String] = None,
path: Option[String] = None
) =
RTDE.DELETE(
desc = desc.getOrElse(“Terminology.delete”),
url = Terminology.path + path.getOrElse( TERMINOLOGY_PATH.value )
)

}
`

This allows me to write my scenarios very simply:

val scn = scenario( name ) .exec( Login.sequence ) .exec( Service.method ) .exec( Service2.method2( parameters ) )

Still to come: Build reusable chains that represent specific user or application flows, and then the simulation can mix and match various scenarios together.

As Stéphane mentioned, I’ve also been playing around with creating my own DSL extensions. For example, I have a SessionManagement module that lets me set variables in the session using syntax like this:

`
scenario( name )
.set( VAR, VALUE ) // value of expression is interpolated and stored in VAR
.set( VAR, $(VAR2) ) // contents of VAR2 are stored in VAR
.set( VAR, VAR2.value ) // contents of VAR2 are stored in VAR
.set( VAR, LIST.random ) // one of the elements of LIST are stored in VAR
.set( VAR ).from( VAR2 ) // contents of VAR2 are stored in VAR
.set( VAR ).to( VALUE ) // value of expression is interpolated and stored in VAR
.set( VAR ).to( $(VAR2) ) // contents of VAR2 are stored in VAR
.set( VAR ).to.value( VALUE ) // value of expression is interpolated and stored in VAR
.set( VAR ).to.value( $(VAR2) ) // contents of VAR2 are stored in VAR
.set( VAR ).to.value.of( VAR2 ) // contents of VAR2 are stored in VAR
.set( VAR ).to.value.of( PATH ).from( VAR2 ) // extract first matching JSON PATH from VAR2
.set( VAR ).to.list.of( PATH ).from( VAR2 ) // extract ALL matching JSON PATH from VAR2
.set( VAR ).to.first( SOME_LIST ) // extract the first element from SOME_LIST into VAR
.set( VAR ).to.last( SOME_LIST ) // extract the last element into VAR
.set( VAR ).to.random( SOME_LIST ) // extract any element at random

`

If you are interested in the code to do that, let me know.

The principles that let me build that kind of DSL could be used to define custom DSL for an application, too. But I opted not to do that for a very good reason. I wanted to be able to use my API wrappers inside of things like .resources( http(), … ). If you write DSL which extends the chain, you can’t (that I know of) use that DSL inside of a resources() method on an HTTP request. And since my application makes restful calls as resources…

Thank you both,
But given the time constraints in my Project I would have to og for the simle (and not so elegant) way of implementing my simulation.
I need to Call the token server for every request and I will also do so, but how do I add a token to each .exec?

I canot do this:

`
val httpConf = http
.baseURL(https://sweet.smallfirm.com)
.acceptEncodingHeader(“gzip,deflate”)

val scn = scenario(“Scenario”)
.baseHeaders(Map(“X-Token” → “1234asdf”))
.feed(csv(“memberInfo.csv”).random)
.exec(
http(“getProfile”)
.get("/user/profile")
.check(status.is(200))
.check(jsonPath("$.resultCode").is(“SUCCESS”))
.check(jsonPath("$.profile.memberships[0].scanNumber").saveAs(“scanNr”)))
`

The scenario is like this:

  1. Call the token server with user name and password and get a token in return.
  2. Make another request adding the token as part of the request
  3. Make a last request with the token as part of the request
    Magnus

I have my doubts that you can modify baseHeaders on a per-user basis. But I have not looked at the code to confirm.

With only three requests total in your scenario, doing it manually is the easiest solution. Extract the token and put it in session. Then inject it into the follow-on requests in the appropriate way, such as by adding a .header( “X-Token” → “${theToken}” ) Anything more fancy than that will add more code than it saves.

Mind you, you COULD extend the http method with a “addToken” method, like so:

`
object SOMETHING {
implicit class AddTokenToHttpRequest( request : T <: AbstractHttpRequestBuilder[T] ) {
def addToken = request.header( “X-Token” → “${theToken}” )
}
}

// inside your scenario
import SOMETHING._

http( url )
.get( path )
.addToken

`

It’s up to you to decide if the extra code to make it work is worth the effort to make your code more readable.

Hi, again,
I want to test this way of using the token:

.exec(
http(“getProfile”)
.get("/user/profile")
.header(“X-Token” → “1234asdf”)
.check(status.is(200))

Until now the token “1234asdf” wil work (out auth service is under development).

However saying:

.header(“X-Token” → “1234asdf”)

Result in error in IntelliJ (Unspecified value parameters: value:session.Expression:[String]

Do I define the .header wrong?

When the auth service is implemented I would probably extract the token from the response with something like this:

.check(jsonPath("$.service.auth[0].token").saveAs(“sometoken”)))

and reuse it as this:

header( “X-Token” → “${sometoken}” )

But how do i “hard-code” the token in the .exec for now?

Thanx!

Magnus

Either the standard Predef._ imports are missing, or IntelliJ is crap.

IntelliJ is crap, this works:

val scn = scenario(“Scenario”)

.feed(csv(“memberInfo.csv”).random)
.exec(
http(“getProfile”)
.get("/user/profile")
.header(“X-Token”, “1234asdf”)
.check(status.is(200))

I am not sure if it is still in order to ask a question here but I do have a problem similar to this one but for some reasons I was not able to digest John Arrowwood’s explanation. I need to create a scenario with at least four workflows in it. The first one been the one I am showing here below. The tokenId is returning nothing. What am I doing wrong here? Ideally this tokenId will be used in the next workflow which will be getting admin credentials. Here is what i have so far:

val workflow1Header = Map("username" -> "username", "password" -> "password", "Content-Type" -> "application/json")
  val tokenId = "" //workflow1 return tokenId which I would like to use for my workflow2
  //Do I need to initialize session variable here? such as tokenIdSession?

  val workflowScn = scenario("Admin token")
    .exec(http("Workflow1")
      .post("/admin token endpoint")
      .headers(workflow1Header)
      .check(regex("token: (\\d+)").find.saveAs("tokenId"))
    )
//I tried to use session here but I kept on getting errors and my scenario is returning this error message "ailed: regex(token: (\d+)).find.exists, found nothing"

  setUp(workflowScn.inject(atOnceUsers(1)))
    .protocols(newAcctHttpConf)