如何在 Gatling 负载测试中为多个虚拟用户使用单个 OAuth2.0 令牌
How to use a single OAuth2.0 token for Multiple Virtual Users in a Gatling load test
我需要通过 Gatling 对需要 OAuth2.0 令牌的 API 进行负载测试(我完全是新手!),但希望每个虚拟用户都使用相同的令牌。我正在检索令牌(我认为)并将其放入一个名为 'access' 的变量中,但是当测试本身开始时,我一直得到“没有定义名为 'access' 的属性”。
我的令牌检索如下所示(连同下面使用的 httpConf):
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")))
然后我尝试将负载测试设置为(注意:我最初确实放置了 'Map',而不是可变变体,但在某处读到默认值是不可变的,并且想知道这是否就是为什么 header 无法更新):
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))
想法是令牌检索将在负载测试开始之前完成,然后负载测试场景将使用 'access' 变量,但它给出以下结果:
ERROR : Failed to build request: No attribute named 'access' is defined
我已经筋疲力尽了。我猜这可能与范围有关,也许变量不会延续到负载测试场景,但我在其他地方看到的例子似乎完全推荐这种设置,所以我不知道这些其他示例是部分完整的还是什么。
会话是针对每个用户的,用户之间不共享任何会话数据。因此,当您有 1 个用户 运行 设置您的 'auth' 场景并保存令牌时,运行 'scn' 是两个不同的用户,他们无权访问会话授权用户的值。
这不是推荐的做法,但您可以通过将 auth 令牌推送到常规 scala var 并在 auth 场景中设置它并在主场景中读取它来解决这个问题 - 您只需要确保 auth 始终在您注入任何其他用户之前完成。
var token: String = ""
然后在auth场景中,最后有一个步骤,例如
.exec(session => {
token = session("access").as[String]
session
})
然后在 scn 场景的开始有一个设置会话变量的步骤
.exec(session.set("access", token))
我过去使用过这种模式并且它有效,但我确信有更好的方法可以做到这一点
@Tarun,
当我这样做时,我的场景中有 'exec',而不是设置,并使用了以下语法:
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))
)
正如前面讨论的评论中提到的,这是因为我使用的是更新版本的加特林,'get' 方法不再是会话 api 的一部分。
我的完整解决方案如下 - 请注意,有许多需要注意的事项可能不适用于您的解决方案。我使用了一个对象,因为它只是让我对我想做的事情更加清楚!此外,一些导入可能是多余的,因为我将它们作为散弹枪方法的一部分包含在内,以找到有用的东西!
最后,我基本上列出了一个目录的内容,并循环浏览其中列出的文件,将每个文件用作馈线。你看起来好像在使用 json 的文字模板,所以可能不需要它,但我想我会为了完整性而包含它,因为它非常方便 - 如果你更改 [=] 的格式27=],你不需要在模拟中更改模板,你只需清除目录并将新格式的示例放到那里就可以了! :
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
}
我的意思是,当我从中删除敏感内容时,我可能在某处打错了字,但希望这能满足您的需要。
今天我为我的项目实现了这个场景。请查看下面的代码,它也适用于您。
注意:我用 API 的必需参数编写了这段代码。您可以根据您的要求修改此代码。现在,这段代码写在一个 class 中。我已经使用不同的 classes 和 属性 文件以正确的格式实现了这段代码。我也会post那个。
单个class,代码如下:
`
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)
}`
我需要通过 Gatling 对需要 OAuth2.0 令牌的 API 进行负载测试(我完全是新手!),但希望每个虚拟用户都使用相同的令牌。我正在检索令牌(我认为)并将其放入一个名为 'access' 的变量中,但是当测试本身开始时,我一直得到“没有定义名为 'access' 的属性”。
我的令牌检索如下所示(连同下面使用的 httpConf):
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")))
然后我尝试将负载测试设置为(注意:我最初确实放置了 'Map',而不是可变变体,但在某处读到默认值是不可变的,并且想知道这是否就是为什么 header 无法更新):
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))
想法是令牌检索将在负载测试开始之前完成,然后负载测试场景将使用 'access' 变量,但它给出以下结果:
ERROR : Failed to build request: No attribute named 'access' is defined
我已经筋疲力尽了。我猜这可能与范围有关,也许变量不会延续到负载测试场景,但我在其他地方看到的例子似乎完全推荐这种设置,所以我不知道这些其他示例是部分完整的还是什么。
会话是针对每个用户的,用户之间不共享任何会话数据。因此,当您有 1 个用户 运行 设置您的 'auth' 场景并保存令牌时,运行 'scn' 是两个不同的用户,他们无权访问会话授权用户的值。
这不是推荐的做法,但您可以通过将 auth 令牌推送到常规 scala var 并在 auth 场景中设置它并在主场景中读取它来解决这个问题 - 您只需要确保 auth 始终在您注入任何其他用户之前完成。
var token: String = ""
然后在auth场景中,最后有一个步骤,例如
.exec(session => {
token = session("access").as[String]
session
})
然后在 scn 场景的开始有一个设置会话变量的步骤
.exec(session.set("access", token))
我过去使用过这种模式并且它有效,但我确信有更好的方法可以做到这一点
@Tarun,
当我这样做时,我的场景中有 'exec',而不是设置,并使用了以下语法:
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))
)
正如前面讨论的评论中提到的,这是因为我使用的是更新版本的加特林,'get' 方法不再是会话 api 的一部分。
我的完整解决方案如下 - 请注意,有许多需要注意的事项可能不适用于您的解决方案。我使用了一个对象,因为它只是让我对我想做的事情更加清楚!此外,一些导入可能是多余的,因为我将它们作为散弹枪方法的一部分包含在内,以找到有用的东西!
最后,我基本上列出了一个目录的内容,并循环浏览其中列出的文件,将每个文件用作馈线。你看起来好像在使用 json 的文字模板,所以可能不需要它,但我想我会为了完整性而包含它,因为它非常方便 - 如果你更改 [=] 的格式27=],你不需要在模拟中更改模板,你只需清除目录并将新格式的示例放到那里就可以了! :
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
}
我的意思是,当我从中删除敏感内容时,我可能在某处打错了字,但希望这能满足您的需要。
今天我为我的项目实现了这个场景。请查看下面的代码,它也适用于您。
注意:我用 API 的必需参数编写了这段代码。您可以根据您的要求修改此代码。现在,这段代码写在一个 class 中。我已经使用不同的 classes 和 属性 文件以正确的格式实现了这段代码。我也会post那个。
单个class,代码如下:
`
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)
}`