I'm completely new to Scala and Gatling, please forgive the basic question!
I want to create an http protocol with baseUrl specified by the result of an initial http request. Or in other words:
Get remote config as JSON, let's say from https://example.com/config.json
Parse JSON, retrieve a specified property endpoint
Pass that value to http.baseUrl()
I can do this on every single scenario manually but this quickly becomes tedious (and is unnecessarily repetitive). I'd like to find a solution where I can perform this setup once at the beginning of the test run.
My instinct is to go for something like this:
object Environment {
val config = "https://example.com/config.json"
}
val httpProtocol = http("config")
.get(Environment.config)
.check(
jsonPath("$.endpoint").saveAs("endpoint")
)
.baseUrl("${endpoint}")
// ... and then later on
setUp(
// scenario.inject()…
).protocols(httpProtocol)
... but that doesn't compile.
Thanks very much for any help.
What you're proposing won't work.
.protocols takes a HttpProtocolBuilder (documented on the gatling site), whereas you're trying to pass a HttpRequestBuilder
Furthermore, the baseUrl parameter of HttpProtocolBuilder only takes a string, so you won't be able to pass a gatling session value into it.
The only way I can think to do this would be to make the request that returns the base url in the 'before' block, but you won't be able to use gatling dsl constructs to make that request: you'll have to do it with raw scala
Related
In Akka Http, it is possible to define the route system to manage a REST infrastructure in this way, as stated here: https://doc.akka.io/docs/akka-http/current/routing-dsl/overview.html
val route =
get {
pathSingleSlash {
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
} ~
path("ping") {
complete("PONG!")
} ~
path("crash") {
sys.error("BOOM!")
}
}
Is there a way to programmatically invoke one of the route inside the same application, in a way that could be similar to the following statement?
val response = (new Invoker(route = route, method = "GET", url = "/ping", body = null)).Invoke()
where Response would be the same result of a remote HTTP call to the service?
The aforementioned API it's only to give an idea of what I have in mind, I would expect the capability to set the content type, headers, and so on.
In the end I managed to find out the answer to my own question by digging a bit more in Akka HTTP documentation.
As stated here: https://doc.akka.io/docs/akka-http/current/routing-dsl/routes.html, the Route is a type defined as follows:
type Route = RequestContext => Future[RouteResult]
where RequestContext is a wrapper for the HttpRequest. But is true as well that a Route can be converted, implicitly or not, to other function types, like this:
def asyncHandler(route: Route)(...): HttpRequest ⇒ Future[HttpResponse]
Hence, it is indeed possible to "call" a route by converting it to another function type, and then simply passing a HttpRequest build ad hoc, receiving a Future containing the desired response. The conversion required a little more time than the rest of the operations, but it's something that could be done while bootrstrapping the application.
Note: the conversion requires these imports, as stated here: https://doc.akka.io/docs/akka-http/current/introduction.html
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
But these imports are already mandatory for the create of the service itself.
If this is for unit tests, you can use akka-http's test kit.
If this is for the application itself, you should not go through the route, you should just invoke the relevant services that the controller would use directly. If that is inconvenient (too much copy-pasta), refactor until it becomes possible.
As for the reason, I want my application to be wrapped inside both a web server (then use the route the “normal” way) and a daemon that responds to a message broker inbound message.
I have an application that does something like that actually.
But I came at this from the other way: I consider the broker message to be the "primary" format. It is "routed" inside of the consumer based purely on properties of the message itself (body contents, message key, topic name). The HTTP gateway is built on top of that: It has only a very limited number of API endpoints and routes (mostly for caller convenience, might as well have just a single one) and constructs a message that it then passes off to the message consumer (in my case, via the broker actually, so that the HTTP gateway does not even have to be on the same host as the consumer).
As a result, I don't have to "re-use" the HTTP route because that does not really do anything. All the shared processing logic happens at the lower level (inside the service, inside the consumer).
I'm trying to concatenate a vector into a string in order to insert it into a get call.
Previously in my code I use the following:
.exec(http("request_11")
.get("/api/data/users/${userId}")
.headers(headers_3)
.check(jsonPath("$..users[0].links.orgRoles[*]").findAll.saveAs("orgRoles")))
Which gives me an orgRoles session variable of:
Vector(b2b5fb81-4025-48a5-9890-a6ec7d64f317, 744db047-1477-4bb6-9c55-70071ce41cb8, 423b0b07-7bfa-416a-9b95-cc1d75d7d781)
I'd then like to use this concatenated session variable like so:
.exec(http("request_50")
.get("/api/data/orgRoles/${orgRoles}")
Which would, in effect, do the following get call:
.exec(http("request_50")
.get("/api/data/orgRoles/b2b5fb81-4025-48a5-9890-a6ec7d64f317,744db047-1477-4bb6-9c55-70071ce41cb8,423b0b07-7bfa-416a-9b95-cc1d75d7d781")
.headers(headers_3))
I'm brand new to Gatling and Scala, and have done a ton of research. I found the following answer which offers that
session => session("itemIds").validate[Seq[String]].map(_.mkString(","))
is the right approach. In turn, I've tried to manipulate the session variable with the following:
...
.exec { session =>
session("orgRoles").map(_.mkString(","))
}
.exec(http("request_50")
.get("/api/data/orgRoles/${orgRoles}")
.headers(headers_3))
...
Yet end up with the error:
value mkString is not a member of io.gatling.core.session.SessionAttribute
Any suggestions on moving forward?
The error you are getting is because by:
session("orgRoles")
you are calling Session.apply method at session instance (for more see source code). The apply method returns SessionAttribute instance, which has following methods:
// Use when you are certain that key exists in session, otherwise you'll get exception which you has to handle
def as[T: NotNothing]: T
// Use when you want to handle also not existing key and you can react to `None` case
def asOption[T: TypeCaster: ClassTag: NotNothing]: Option[T]
// Use when you want to code against success path and use combinators provided by `Validation`
def validate[T: TypeCaster: ClassTag: NotNothing]: Validation[T]
Each servers different purpose (see comments). All above mentioned methods of SessionAttribute are just getters from session. Their only purpose is to provide you a value of the key stored in session in any form.
Only after getting the value you can do anything with it. The safest bet is to use validate method which provides common combinators (used in all Scala collections) map and flatMap to manipulate the value when it exist. You can find more about Validation concept here. Hence your code should look like:
// Just getting from session and concatenating
session => session("orgRoles").validate[Seq[String]].map( _.mkString(",") )
// also storing back to session
session => session.set("orgRoles", session("orgRoles").validate[Seq[String]].map( _.mkString(",") ))
This is also what you already wrote above. But you are already using check DSL method and thus you can use transform() directly in your first call as:
.exec(
http("request_11")
.get("/api/data/users/${userId}")
.headers(headers_3)
.check(
jsonPath("$..users[0].links.orgRoles[*]")
.findAll
.transform( _.mkString(",") )
.saveAs("orgRoles")
)
)
It's straightforward, easier to follow and there is no temporary save to Session. Even the function which you put to transform is easier, while it is defined as A => B thus plain transformation. Check-out more about transforming here.
Have you tried calling the toString or maybe groupHierarchy?
session => session("itemIds").validate[Seq[String]].map(_.toString.mkString(","))
session => session("itemIds").validate[Seq[String]].map(_.groupHierarchy.mkString(","))
take a look at: https://github.com/gatling/gatling/blob/master/gatling-core/src/main/scala/io/gatling/core/session/Session.scala
I ended up solving it with this:
.exec(session =>
session.set("orgRolesConcat", session("orgRoles").as[Seq[String]].mkString(",")))
I currently have a REST call set up using spray pipeline. If I don't get a response within x number of seconds, I want it to timeout, but only on that particular call. When making a spray client pipeline request, is there a good way to specify a timeout specific to that particular call?
As far as I can tell, as of spray-client 1.3.1 there is no way to customise the pipe after it has been created.
However, you can create custom pipes for different types of requests.
It's worth mentioning the fact that the timeouts defined below are the timeouts for the ask() calls, not for the network operations, but I guess this is what you need from your description.
I found the following article very useful in understanding a bit better how the library works behind the scenes: http://kamon.io/teamblog/2014/11/02/understanding-spray-client-timeout-settings/
Disclaimer: I haven't actually tried this, but I guess it should work:
val timeout1 = Timeout(5 minutes)
val timeout2 = Timeout(1 minutes)
val pipeline1: HttpRequest => Future[HttpResponse] = sendReceive(implicitly[ActorRefFactory],
implicitly[ExecutionContext], timeout1)
val pipeline2: HttpRequest => Future[HttpResponse] = sendReceive(implicitly[ActorRefFactory],
implicitly[ExecutionContext], timeout2)
and then you obviously use the appropriate pipe for each request
I'm exploring the Play Framework and have run in to a bit of a corner.
After making an API call to Google Analytics with the WS library, I receive a Future[Response] object. After digesting that Response object, I get the data I really care about, but because it's wrapped in a Future, I'm having some trouble writing it to the browser.
OK( gaApiString )
This gives me an error that reads:
Cannot write an instance of scala.concurrent.Future[String] to HTTP
response. Try to define a Writeable[scala.concurrent.Future[String]]
I'm having some trouble finding & understanding how to use a Writable object. Little help?
You need to map the Future to a Future[Result], passing it to Action.async.
def test = Action.async {
val wsResult: Future[String] = ...
wsResult.map { gaApiString =>
Ok(gaApiString)
}
}
If gaApiString is actually List[String], then it depends on what you want to do with it. Displaying it as a comma-separated list you could just change it to Ok(gaApiString.mkString(",")). The key here is mapping the Future to manipulate the value after it's completed.
Hi scala and spray people!
I have a small annoying issue with extracting the HTTP 'Accept' header from the RequestContext and matching on it. On a normal route like so:
get {
respondWithMediaType(`text/plain`) {
complete ( "Hello World!" )
}
}
It works like a charm. But whenever I bring the context into scope like so (as suggested in the documentation for directives):
get { context => {
respondWithMediaType(`text/plain`) {
complete ( "Hello World!" )
}
} }
The result becomes the following error message:
The server was not able to produce a timely response to your request.
I am fairly new to Spray, but it looks really odd to me that bringing an (otherwise implicit) object into scope can have such a weird sideeffect. Does any of you have a clue on what is going on?
Direct access to the RequestContext is rarely needed. In fact, you only need it if you want to write custom directives. Common tasks and extracting the usual bits of data can normally be handled using one of the predefined directives.
It seems what you want to do is manual content type negotiation. In fact, you don't have to do it manually as spray does content type automatically for common data structures. Your example can be shortened to
get {
complete("Hello World!")
}
When complete is called with a string the response will always be of type text/plain. If the client would send a request with an Accept header that doesn't accept text/plain the request would already be rejected by the server.
If you want to customize the kinds of content types that can be provided from a Scala data-type you need to provide a custom Marshaller. See the documentation on how to achieve that.
Answering your original question why adding context => makes the request timeout: This is because the predefined directives already are of type RequestContext => Unit. So, writing
respondWithMediaType(`text/plain`) {
complete("Hello World!")
}
is exactly equivalent to (i.e. automatically expanded to)
ctx => respondWithMediaType(`text/plain`) {
complete("Hello World!")
}.apply(ctx)
So, if you add only ctx => manually, but don't add the apply call, an incoming request is never fed into the inner route and therefore never completed. The compiler doesn't catch this kind of error because the type of a route is RequestContext => Unit and so the variant with and the variant without the apply invocation are both valid. We are going to improve this in the future.
See the documentation for more info about how routes are built.
Finally, if you need to extract a header or its value you can use one of the predefined HeaderDirectives that simplify working with request headers a lot.