I use The Play WS API from PlayFramework to communicate with external API.
I need to process the received data, but don't know how. I get a response, and I want to pass it to other function like an JSON Object. How I can achieve that?
Code I use you can see below.
Thanks!
def getTasks = Action {
Async {
val promise = WS.url(getAppProperty("helpdesk.host")).withHeaders(
"Accept" -> "application/json",
"Authorization" -> "Basic bi5sdWJ5YW5vdjoyMDEzMDcwNDE0NDc=" ).get()
for {
response <- promise
} yield Ok((response.json \\ "Tasks"))
}
}
I get a response, and I want to pass it to other function like an JSON Object.
I'm not sure I understand your question, but I'm guessing you want to transform the json you receive from the WS call prior to returning to the client, and that this transformation could take several lines of code. If this is correct, then you just need to add curly brackets around your yield statement so you can do more work on the response:
def getTasks = Action {
Async {
val promise = WS.url(getAppProperty("helpdesk.host")).withHeaders(
"Accept" -> "application/json",
"Authorization" -> "Basic bi5sdWJ5YW5vdjoyMDEzMDcwNDE0NDc=" ).get()
for {
response <- promise
} yield {
// here you can have as many lines of code as you want,
// only the result of the last line is yielded
val transformed = someTransformation(response.json)
Ok(transformed)
}
}
}
I took a look at the doc, and you could try:
Async {
WS.url(getAppProperty("helpdesk.host")).withHeaders(
"Accept" -> "application/json",
"Authorization" -> "Basic bi5sdWJ5YW5vdjoyMDEzMDcwNDE0NDc=" ).get().map{
response => Ok(response.json \\ "Tasks")
}
}
Related
I am getting the following error when I attempt to read the body from the http client response object. I don't get the exception all the time so I guess it's a threading issue related to the CompletableFuture. Any idea of what I am doing wrong? I use vert.x 3.8.1
java.lang.IllegalStateException
at io.vertx.core.http.impl.HttpClientResponseImpl.checkEnded(HttpClientResponseImpl.java:134)
at io.vertx.core.http.impl.HttpClientResponseImpl.endHandler(HttpClientResponseImpl.java:153)
at io.vertx.core.http.impl.HttpClientResponseImpl.bodyHandler(HttpClientResponseImpl.java:193)
at com.diagnostics.Response.body(Web.kt:116)
at com.diagnostics.Response.bodyNow(Web.kt:111)
at com.diagnostics.Response.bodyNow$default(Web.kt:110)
at com.diagnostics.Main.postVerificationTest(Main.kt:73)
at com.diagnostics.Main.main(Main.kt:52)
at com.diagnostics.Main.main(Main.kt)
Code that throws the exception:
val response = client.get(requestUri = "/api/info").get(3, TimeUnit.SECONDS)
val expectedStatus = 200
assertConditionOrExit(pvtLog, response.status == expectedStatus, "response status is ${response.status} expecting $expectedStatus")
val body = response.bodyNow()
assertConditionOrExit(pvtLog, body.isNotEmpty(), "body is empty expecting a non empty value")
The http client response object is created from the following code:
private fun request(
method: HttpMethod,
port: Int,
host: String,
requestUri: String
): CompletableFuture<Response> {
val future = CompletableFuture<Response>()
httpClient.request(method, port, host, requestUri)
.exceptionHandler { future.completeExceptionally(it) }
.handler { resp -> future.complete(Response(resp)) }
.end()
return future
}
And the body is retrieved...
fun bodyNow(timeout: Long = 10, unit: TimeUnit = SECONDS): String {
return body().get(30000, SECONDS)
}
fun body(): CompletableFuture<String> {
val future = CompletableFuture<String>()
resp.bodyHandler { buff -> future.complete(buff.toString())}
return future
}
The body() function sets a bodyHandler after the HttpClientRequest handler has been invoked in the request() method.
So there is a chance that, while your main thread proceeds, the eventloop receives content and drops it. If content is small, the request could even end before the bodyHandler is set.
This is why you only see the exception from time to time.
If you want to set the bodyHandler later, you must pause the HttpClientResponse:
httpClient.request(method, port, host, requestUri)
.exceptionHandler { future.completeExceptionally(it) }
.handler { resp ->
resp.pause() // Pause the response
future.complete(Response(resp))
}
.end()
Then resume it after setting the bodyHandler:
resp.bodyHandler { buff -> future.complete(buff.toString())}
resp.resume()
So I'm trying to call a second endpoint in one of my Vapor endpoints. I have one endpoint that is just a get and works good:
router.get("status") { req -> Future<ConnectionResponse> in
let client = try req.make(Client.self)
let response = client.get("https://url.com/endpoint/")
return response.flatMap(to: ConnectionResponse.self, { response in
return try response.content.decode(ConnectionResponse.self)
})
}
This returns correctly a ConnectionResponse json. When I try to do the same, but in a POST that needs some parameters, I can't figure out why the compiler doesn't let me run:
router.post("purchase") { req -> Future<ConnectionResponse> in
return try req.content.decode(PurchaseRequest.self).map(to: ConnectionResponse.self, { response in
let client = try req.make(Client.self)
let response = client.get("https://url.com/endpoint/")
return response.flatMap(to: ConnectionResponse.self, { response in
return try response.content.decode(ConnectionResponse.self)
})
})
}
It fails on the flatMap saying Cannot convert return expression of type 'EventLoopFuture<ConnectionResponse>' to return type 'ConnectionResponse'.
As you can see, the purchase GET call is the same as the status apart from the initial decoding of the POST parameters. What am I doing wrong?
Simply replace
return try req.content.decode(PurchaseRequest.self).map(to: ConnectionResponse.self, { response in
with
return try req.content.decode(PurchaseRequest.self).flatMap(to: ConnectionResponse.self, { response in
Complete working code may look like this
router.post("purchase") { req -> Future<ConnectionResponse> in
return try req.content.decode(PurchaseRequest.self).flatMap { response in
let client = try req.make(Client.self)
let response = client.get("https://url.com/endpoint/")
return response.flatMap { response in
return try response.content.decode(ConnectionResponse.self)
}
}
}
So what is the difference between map and flatMap?
flatMap is used if next result is Future<Something> e.g.:
someDatabaseCall(on: container).flatMap { databaseResult1 in
/// it will return Future<DatabaseResult>
/// that's why we use `flatMap` above instead of `map`
return anotherDatabaseCall(on: container)
}
map is for non-future results e.g.:
someDatabaseCall(on: container).map { databaseResult1 in
/// it will return non-future result
/// that's we use `map` above instead of `flatMap`
return "hello world" // just simple string
}
What is Future<> ? It's just a promise, like an object with callback.
In the following code, I am getting a token in the first Gatling request, saving it in a variable named auth. However, when I try to use it in the second request, it is sending empty string in place of auth variable. So for some reason, the auth string is not being updated till the time it is being used in the second request. Can anyone suggest any workaround so that I can use the value returned in one request into another request?
Code:
val headers_10 = Map("Content-Type" -> "application/x-www-form-urlencoded")
var a= "qwerty91#gmail.com"
var auth = ""
val scn = scenario("Scenario Name") // A scenario is a chain of requests and pauses
.exec(http("request_1") // Here's an example of a POST request
.post("/token")
.headers(headers_10)
.formParam("email", a)
.formParam("password", "password")
.transformResponse { case response if response.isReceived =>
new ResponseWrapper(response) {
val a = response.body.string
auth = "Basic " + Base64.getEncoder.encodeToString((a.substring(10,a.length - 2) + ":" + "junk").getBytes(StandardCharsets.UTF_8))
}
})
.pause(2)
.exec(http("request_2")
.get("/user")
.header("Authorization",auth)
.transformResponse { case response if response.isReceived =>
new ResponseWrapper(response) {
val a = response.body.string
}
})
You should store the value you need in the session. Something like this will work, although you'll have to tweak the regex and maybe some other details:
val headers_10 = Map("Content-Type" -> "application/x-www-form-urlencoded")
var a= "qwerty91#gmail.com"
var auth = ""
val scn = scenario("Scenario Name") // A scenario is a chain of requests and pauses
.exec(http("request_1") // Here's an example of a POST request
.post("/token")
.headers(headers_10)
.formParam("email", a)
.formParam("password", "password")
.check(regex("token: (\\d+)").find.saveAs("auth")))
.pause(2)
.exec(http("request_2")
.get("/user")
.header("Authorization", "${auth}"))
Here's the documentation on "checks", which you can use to capture values from a response:
http://gatling.io/docs/2.2.2/http/http_check.html
Here is the documentation on the gatling EL, which is the easiest way to use session variables (this is the "${auth}" syntax in the last line above):
http://gatling.io/docs/2.2.2/session/expression_el.html
I am trying to define a webservice, using Scalatra, where the parameters are passed in in the body, preferably as JSON, not having everything on the url, as I have it now.
So, I would like this test to pass, but the commented out code is what passes currently. The non-commented code isn't JSON, but I also am not certain how I would pass JSON for testing, as put requires an Iterable in the second parameter.
class WebAppSpec extends MutableScalatraSpec {
addServlet(classOf[WebApp], "/*")
"PUT /phaseupdate" should {
"return status 200" in {
//put("/phaseupdate/test1/address1/starting/10") {
put("/phaseupdate", Map("filename" -> "test1", "entryaddress" -> "address1","name" -> "starting","percentcomplete" -> "10")) {
status must_== 200
}
}
}
My current definition, which is wrong, is:
put("/phaseupdate/:filename/:entryaddress/:name/:percentcomplete") {
val filename = params("filename")
val entryaddress = params("entryaddress")
val name = params("name")
val percentcomplete = params("percentcomplete")
So how do I define my put service to just call it with PUT /phaseupdate and have the parameters in the body?
I am trying to limit what is going into the webserver access log, basically.
The solution is to do this:
put("/phaseupdate") {
val filename = if (params("filename").indexOf('.') > -1) params("filename").substring(0, params("filename").indexOf('.')) else params("filename")
val entryaddress = params("entryaddress")
val name = params("name")
val percentcomplete = params("percentcomplete")
Basically, params() can read in what was passed.
The specs2 test is, and if this is followed by the get it returns the correct information.
"PUT /phaseupdate" should {
"return status 200" in {
put("/phaseupdate", Map("filename" -> "test1", "entryaddress" -> "address1", "name" -> "starting", "percentcomplete" -> "10")) {
status must_== 200
}
}
}
I am using databinder dispatch for making HTTP requests which works nicely, as long as the web server returns a 404.
If the request fails, the web server returns a 403 status code and provides a detailed error message in the response body as XML.
How to read the xml body (regardless of the 403), e.g. how can I make dispatch ignore all 403 errors?
My code looks like this:
class HttpApiService(val apiAccount:ApiAccount) extends ApiService {
val http = new Http
override def baseUrl() = "http://ws.audioscrobbler.com/2.0"
def service(call:Call) : Response = {
val http = new Http
var req = url(baseUrl())
var params = call.getParameterMap(apiAccount)
var response: NodeSeq = Text("")
var request: Request = constructRequest(call, req, params)
// Here a StatusCode exception is thrown.
// Cannot use StatusCode case matching because of GZIP compression
http(request <> {response = _})
//returns the parsed xml response as NodeSeq
Response(response)
}
private def constructRequest(call: Call, req: Request, params: Map[String, String]): Request = {
val request: Request = call match {
case authCall: AuthenticatedCall =>
if (authCall.isWriteRequest) req <<< params else req <<? params
case _ => req <<? params
}
//Enable gzip compression
request.gzip
}
}
I believe something like this works:
val response: Either[String, xml.Elem] =
try {
Right(http(request <> { r => r }))
} catch {
case dispatch.StatusCode(403, contents) =>
Left(contents)
}
The error will be in Left. The success will be in Right. The error is a String that should contain the XML response you desire.
If you need more, I believe you can look at HttpExecutor.x, which should give you full control. It's been a while since I've used dispatch, though.
Also, I'd suggest using more val's and less var's.