HttpClient error checking
An example code to use the built-in JDK HttpClient directly, with a bit of Kotlin.
/**
* Documents a HTTP failure.
* @property statusCode the HTTP status code, one of [jakarta.servlet.http.HttpServletResponse] `SC_*` constants.
* @property method the request method, e.g. `"GET"`
* @property requestUrl the URL requested from the server
* @property response the response body received from the server, may provide further information to the nature of the failure.
* May be blank.
*/
public class HttpResponseException(
public val statusCode: Int,
public val method: String,
public val requestUrl: String,
public val response: String,
cause: Throwable? = null
) : IOException("$statusCode: $response", cause) {
override fun toString(): String = "${javaClass.simpleName}: $message ($method $requestUrl)"
}
/**
* Fails if the response is not in 200..299 range; otherwise returns [this].
* @throws FileNotFoundException if the HTTP response was 404
* @throws HttpResponseException if the response is not in 200..299 ([Response.isSuccessful] returns false)
* @throws IOException on I/O error.
*/
public fun <T> HttpResponse<T>.checkOk(): HttpResponse<T> {
if (!isSuccessful) {
val response = bodyAsString()
if (statusCode() == 404) throw FileNotFoundException("${statusCode()}: $response (${request().method()} ${request().uri()})")
throw HttpResponseException(statusCode(), request().method(), request().uri().toString(), response)
}
return this
}
/**
* True if [HttpResponse.statusCode] is 200..299
*/
public val HttpResponse<*>.isSuccessful: Boolean get() = statusCode() in 200..299
/**
* Returns the [HttpResponse.body] as [String].
*/
public fun HttpResponse<*>.bodyAsString(): String {
return when (val body = body()) {
is String -> body
is ByteArray -> body.toString(Charsets.UTF_8)
is InputStream -> body.buffered().reader().readText()
is Reader -> body.buffered().readText()
is CharArray -> body.concatToString()
else -> body.toString()
}
}
/**
* Runs given [request] synchronously and then runs [responseBlock] with the response body.
* Everything including the [HttpResponse.body] is properly closed afterward.
*
* The [responseBlock] is only called on HTTP 200..299 SUCCESS. [checkOk] is used, to check for
* possible failure reported as HTTP status code, prior calling the block.
* @param responseBlock runs on success. Takes a [HttpResponse] and produces the object of type [T].
* You can use [json], [jsonArray] or other utility methods to convert JSON to a Java object.
* @return whatever has been returned by [responseBlock]
*/
public fun <T> HttpClient.exec(request: HttpRequest, responseBlock: (HttpResponse<InputStream>) -> T): T {
val result = send(request, HttpResponse.BodyHandlers.ofInputStream())
return result.body().use {
result.checkOk()
responseBlock(result)
}
}
Also add the Apache URIBuilder Kotlin API. Now you can write:
val request = "http://localhost:8080/person".buildUrl().buildRequest()
val client = HttpClient()
val response = client.exec(request) { response -> response.bodyAsString() }
Written on October 30, 2023