IllegalStateException thrown when reading the vert.x http client response body - vert.x

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()

Related

What type of exception should I use for a repeated 5XX response codes being thrown in scala?

In short
What type of exception should I throw if a http request keeps returning a 5XX response code in scala?
Long form
I have code that retries http requests because I work with some endpoints that aren't very reliable. These retries tend to work. But sometimes I just keep getting 5xx for 408 errors and need to throw an exception. Currently I have a placeholder of an IOException in the code below.
def requestRetrier
...
try {
responseObject = request(url, headers, payload, requestMethod, connectTimeout, readTimeout)
if ((List(500, 503...) contains responseObject.responseCode) && (retries != 0)) {
responseObject = requestRetrier(
url = url,
headers,
payload,
requestMethod,
connectTimeout,
readTimeout,
retries - 1)
} else if ((retries == 0) && (List(500, 503...) contains responseObject.responseCode)) {
throw new java.io.IOException
}
....
I would just create a new custom exception to be more specific and throw that.
Something like:
case class RetriesExceededException(message: String) extends Exception(message)
Then, you can use it in your example as:
} else if ((retries == 0) && (List(500, 503...) contains responseObject.responseCode)) {
throw RetriesExceededException(s"Failed to get response after $retryCount tries.")
}

Unable to catch exception being thrown from another function

I am expecting the "throw RuntimeException" in ServerHandler to proceed to the catch block in registerAccount when error code 403 pops out from the server, but I am unable to catch the error... below is my code:
LoginRepo.kt:
private fun registerAccount(context: Context, jsonObject: JSONObject, username: String, password: String): Result<LoggedInUser> {
try {
ServerHandler.getInstance(context).makeHttpRequest(
"http://www.mywebpage.com/index.php",
Request.Method.POST,
jsonObject
)
return Result.Success(LoggedInUser(java.util.UUID.randomUUID().toString(), username))
} catch (e: Throwable) {
return Result.Error(IOException("Error registering account: ", e))
}
}
ServerHandler.kt:
#Throws(RuntimeException::class)
fun makeHttpRequest(url: String, method: Int, jsonBody: JSONObject? = null):Any {
// Instantiate the RequestQueue.
Log.d("makeHttpRequest","Sending request!!!!")
var stringRequest = when (method) {
Request.Method.POST ->
object : StringRequest(method, url,
Response.Listener<String> { response ->
Log.d("requestPOST", response)
}, Response.ErrorListener { error ->
#Throws(RuntimeException::class)
when(error.networkResponse.statusCode) {
403 -> {
throw RuntimeException("Username is taken.") //<--RuntimeException
}
else-> {
Log.d("UNHANDLED ERROR:", error.toString())
}
}})
}
}
Error:
java.lang.RuntimeException: Username is taken.
at com.example.inspire.data.ServerHandler$makeHttpRequest$stringRequest$5.onErrorResponse(ServerHandler.kt:75)
I do not know all the details, but it seems that the call ServerHandler.getInstance(context).makeHttpRequest( must be returning instantly (even before any HTTP requests are made).
Just put a logging statement after the call but before the return too see if that is really the case. The HTTP request is probably made later at some point (possibly in another thread), when the registerAccount function has long but exited (and so is the try/catch block defined within).
Due to the asynchronous feature in Volley callbacks, the Android Studio debugger has helped to confirm that registerAccount() has returned the result before makeHttpRequest() has done its job to communicate with the PHP server.
As registerAccount() has returned, throwing RuntimeException("Username is taken.") from makeHttpRequest() has no one left to catch its exceptions, which causes the exception unable to be caught.
In this case, catching exceptions sounds impossible, so I would just rather make a
Toast.makeText(
_context,
"Username already taken!",
Toast.LENGTH_LONG
).show()
instead of throwing exceptions...

Making a route both intermediate and final?

In building an app with PerfectHTTP I've come up against a problem with routing: Routes that are used as intermediate routes cannot also be used as final routes.
When set up as below, some URLs track correctly (/index.html in the example), while some URLs do not (/ in the example).
Is there some special sauce I'm missing here, or is what I'm trying to do simply impossible using Perfect?
import PerfectHTTP
import PerfectHTTPServer
// This does various housekeeping to set things common to all requests.
var routes = Routes() {
request, response in
Log.info(message: "\(request.method) \(request.uri)")
response
.setHeader(.contentType, value: "text/plain")
.next()
}
// For this handler, http://localhost:8181/index.html works,
// but http://localhost:8181/ does not
routes.add(method: .get, uris: ["/", "index.html"]) {
request, response in
response
.appendBody(string: "Hello, world!")
.completed()
}
HTTPServer.launch(.server(name: "localhost", port: 8181, routes: [routes]))
if you want set things common to all requests. use filter.
my routes is working
see more https://perfect.org/docs/filters.html
var routes = Routes()
routes.add(method: .get, uris: ["/", "index.html"]) {
request, response in
response
.appendBody(string: "Hello, world!")
.completed()
}
struct MyFilter: HTTPRequestFilter {
func filter(request: HTTPRequest, response: HTTPResponse, callback: (HTTPRequestFilterResult) -> ()) {
print("request method: \(request.method)")
callback(.continue(request, response))
}
}
do {
let server = HTTPServer()
server.serverName = "testServer"
server.serverPort = 8181
server.addRoutes(routes)
server.setRequestFilters([(MyFilter(), HTTPFilterPriority.high)])
try server.start()
} catch {
fatalError("\(error)") // fatal error launching one of the servers
}

Playframework WS API response processing

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")
}
}

Databinder dispatch: Get uncompressed content of a 403 response

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.