What type of exception should I use for a repeated 5XX response codes being thrown in scala? - 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.")
}

Related

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...

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

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

How to redirect all 404 errors

How would I catch all 404 errors and redirect to /app/index.html (I'm trying to satisfy Angular's need to forward all not found resources to index.html) ? I thought implementing my own middleware would work, but not sure I'm doing it right
public final class ForwardToAngularMiddleware: Middleware {
public func respond(to req: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
do {
let response: Future<Response> = try next.respond(to: req)
return response
} catch let error as AbortError {
if error.status == .notFound && req.http.url.path.starts(with: "/app") {
return Future.map(on: req) { req.redirect(to: "/index.html") }
}
throw error
}
}
}
My program never hits the catch block no matter what URL I send. I am configuring my middleware like this:
middlewares.use(FileMiddleware.self)
middlewares.use(ForwardToAngularMiddleware())
middlewares.use(ErrorMiddleware.self)
middlewares.use(SessionsMiddleware.self)
services.register(middlewares)
You may be hitting a couple of issues here. First, the abort error could be being thrown in a future, in which case you need to add a catchMap to the next.respond(to:) call to catch that case.
It also may not throw (though this is unlikely), so you can try unwrapping the response and checking the status code.
Have you put a breakpoint in to see if it ever hits it etc?

Vapor 2 OR conditional not working on request type check

Trying to allow HTTP method.get and method.delete to be missed in a request check. but it's failing.
func respond(to request: Request, chainingTo next: Responder) throws -> Response {
if request.method != .get || request.method != .delete {
guard let contentType = request.headers["Content-Type"], contentType == "application/json" else {
return try Response(status: .badRequest, json: JSON(node: ["reason": "Invalid data"]))
}
}
return try next.respond(to: request)
}
Expecting to not allow .get or .delete to pass through.
Current result is that it goes straight through to invalid when either .get or .delete to show invalid data. It looks like the || operator is being ignored and I can't figure out why.
You should be using the AND (&&) operator. With the way your code is currently written, it follows thus:
A GET request comes from client and enters respond method.
We get to the if-statement.
Check the request method is not equal to .get. It is equal, so we move to the next case.
Check the request method is not equal to .delete. It is not equal, so the statement passes.
We run the code in the if-statement.
Using the && operator will fail if either statement is false, whereas the || operator will fail if both statements are false.
if request.method != .get && request.method != .delete {
guard let contentType = request.headers["Content-Type"], contentType == "application/json" else {
return try Response(status: .badRequest, json: JSON(node: ["reason": "Invalid data"]))
}
}
Note: You don't need the comma after .get. I honestly don't know what it does or if it even makes a difference.

SwiftyJSON - no access to JSON data

I'm trying to parse server response produced by following code (api for rails app developed with grape)
if user.persisted?
return user.to_json
else
error!(user.errors.to_json, 400)
end
When user submit wrong data i see following response from server:
{
response = "{\"email\":[\"is invalid\"],\"password\":[\"is too short (minimum is 8 characters)\"]}";
"response_type" = error;
}
I use on client side following code:
API.registerUser(email: emailTextField.text, password: passwordTextField.text, password_confirmation: passwordConfirmationTextField.text) {
(data, error, statusCode) in
var errorMessage: String
if (data == nil && statusCode == nil) {
errorMessage = "Server error. Please try again later"
} else if (statusCode == 400) {
var errors = JSON(data!)["response"]
errorMessage = "Registration error: \n"
// Display error notice with details
errorMessage = errorMessage + errors.stringValue
}
NSLog(errorMessage)
}
In this code errors variable contains "{\"email\":[\"is invalid\"],\"password\":[\"is too short (minimum is 8 characters)\"]}" but i cannot use errors["email"] - it contains no data. How can i loop thru errors and access all data?
Yeah, you need to parse the response key in order to read it as a dictionary. It also looks like you might have to unescape the response before you parse it unless your parser takes care of that for you.
the problem was on a server side, because it generated escaped json. I've fixed it with following
error!(user.errors, 400)
It was my mistake :)