Gatling: token service and structure of simulation

2,681 views
Skip to first unread message

Magnus Jensen

unread,
Oct 13, 2014, 6:56:58 AM10/13/14
to gat...@googlegroups.com
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

Stéphane Landelle

unread,
Oct 13, 2014, 7:18:30 AM10/13/14
to gat...@googlegroups.com
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.

--
You received this message because you are subscribed to the Google Groups "Gatling User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gatling+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Magnus Jensen

unread,
Oct 13, 2014, 7:19:27 AM10/13/14
to gat...@googlegroups.com
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.

Magnus Jensen

unread,
Oct 13, 2014, 8:01:57 AM10/13/14
to gat...@googlegroups.com
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

Stéphane Landelle

unread,
Oct 13, 2014, 8:10:52 AM10/13/14
to gat...@googlegroups.com
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.

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

Magnus Jensen

unread,
Oct 13, 2014, 8:34:08 AM10/13/14
to gat...@googlegroups.com
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("""<meta content="(.*?)" name="token">""").saveAs("token"))
  .exec(
      http("getCSomething")
        .get("/my/stuff")
        .param("token", "${token}")
   .
.
.
.


John Arrowwood

unread,
Oct 14, 2014, 4:17:39 PM10/14/14
to gat...@googlegroups.com
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...

Magnus Jensen

unread,
Oct 15, 2014, 9:29:46 AM10/15/14
to gat...@googlegroups.com
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

    outref
<span style="color: #660;" class="styled-by-pret
...

John Arrowwood

unread,
Oct 15, 2014, 12:04:39 PM10/15/14
to gat...@googlegroups.com
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.

Magnus Jensen

unread,
Oct 20, 2014, 7:09:32 AM10/20/14
to gat...@googlegroups.com
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








On Monday, October 13, 2014 12:56:58 PM UTC+2, Magnus Jensen wrote:

Stéphane Landelle

unread,
Oct 20, 2014, 7:15:05 AM10/20/14
to gat...@googlegroups.com
Either the standard Predef._ imports are missing, or IntelliJ is crap.

--

Magnus Jensen

unread,
Oct 20, 2014, 7:17:41 AM10/20/14
to gat...@googlegroups.com
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))

Gatlin_Amateaur

unread,
Mar 22, 2017, 4:32:48 PM3/22/17
to Gatling User Group
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)
Reply all
Reply to author
Forward
0 new messages