problem: single invalid response from upstream breaks whole batch

solution: handle http and json errors and convert to RPC errors
This commit is contained in:
Igor Artamonov
2020-05-25 18:30:55 -04:00
parent d7bb121ef0
commit 5584858e44
4 changed files with 97 additions and 44 deletions

View File

@@ -15,9 +15,10 @@
*/
package io.emeraldpay.dshackle.upstream.rpcclient
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.config.AuthConfig
import io.emeraldpay.dshackle.reader.Reader
import io.infinitape.etherjar.rpc.RpcException
import io.infinitape.etherjar.rpc.RpcResponseError
import io.netty.buffer.Unpooled
import io.netty.handler.codec.http.HttpHeaderNames
import io.netty.handler.codec.http.HttpHeaders
@@ -25,7 +26,6 @@ import io.netty.handler.ssl.SslContextBuilder
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono
import reactor.netty.http.client.HttpClient
import reactor.netty.tcp.SslProvider
import java.io.ByteArrayInputStream
import java.security.KeyStore
import java.security.cert.CertificateFactory
@@ -86,9 +86,13 @@ class JsonRpcHttpClient(
.uri(target)
.send(Mono.just(request).map { Unpooled.wrappedBuffer(it) })
return response.responseContent()
.aggregate()
.asByteArray()
return response.response { header, bytes ->
if (header.status().code() != 200) {
Mono.error(RpcException(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "HTTP Code: ${header.status().code()}"))
} else {
bytes.aggregate().asByteArray()
}
}.single()
}
override fun read(key: JsonRpcRequest): Mono<JsonRpcResponse> {
@@ -96,5 +100,13 @@ class JsonRpcHttpClient(
.map(JsonRpcRequest::toJson)
.flatMap(this@JsonRpcHttpClient::execute)
.map(parser::parse)
.onErrorResume { t ->
val err = if (t is RpcException) {
JsonRpcResponse.error(t.code, t.rpcMessage)
} else {
JsonRpcResponse.error(1, t.message ?: t.javaClass.name)
}
Mono.just(err)
}
}
}

View File

@@ -16,6 +16,7 @@
package io.emeraldpay.dshackle.upstream.rpcclient
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import io.infinitape.etherjar.rpc.RpcResponseError
@@ -30,48 +31,52 @@ class JsonRpcParser() {
private val jsonFactory = JsonFactory()
fun parse(json: ByteArray): JsonRpcResponse {
val parser: JsonParser = jsonFactory.createParser(json)
parser.nextToken()
if (parser.currentToken != JsonToken.START_OBJECT) {
return JsonRpcResponse(null, JsonRpcResponse.ResponseError(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Invalid JSON"))
}
var nullResponse: JsonRpcResponse? = null
while (parser.nextToken() != JsonToken.END_OBJECT) {
val field = parser.currentName
if (field == "jsonrpc" || field == "id") {
if (!parser.nextToken().isScalarValue) {
return JsonRpcResponse(null, JsonRpcResponse.ResponseError(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Invalid JSON (id/type)"))
}
// just skip the field
} else if (field == "result") {
val value = parser.nextToken()
val start = parser.tokenLocation
if (value.isScalarValue) {
val text = parser.text
if (value == JsonToken.VALUE_STRING) {
return JsonRpcResponse(("\"" + text + "\"").toByteArray(), null)
} else if (value == JsonToken.VALUE_NULL) {
//if null we should check if error is present
nullResponse = JsonRpcResponse(text.toByteArray(), null)
} else {
return JsonRpcResponse(text.toByteArray(), null)
try {
val parser: JsonParser = jsonFactory.createParser(json)
parser.nextToken()
if (parser.currentToken != JsonToken.START_OBJECT) {
return JsonRpcResponse(null, JsonRpcResponse.ResponseError(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Invalid JSON"))
}
var nullResponse: JsonRpcResponse? = null
while (parser.nextToken() != JsonToken.END_OBJECT) {
val field = parser.currentName
if (field == "jsonrpc" || field == "id") {
if (!parser.nextToken().isScalarValue) {
return JsonRpcResponse(null, JsonRpcResponse.ResponseError(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Invalid JSON (id/type)"))
}
// just skip the field
} else if (field == "result") {
val value = parser.nextToken()
val start = parser.tokenLocation
if (value.isScalarValue) {
val text = parser.text
if (value == JsonToken.VALUE_STRING) {
return JsonRpcResponse(("\"" + text + "\"").toByteArray(), null)
} else if (value == JsonToken.VALUE_NULL) {
//if null we should check if error is present
nullResponse = JsonRpcResponse(text.toByteArray(), null)
} else {
return JsonRpcResponse(text.toByteArray(), null)
}
} else if (value == JsonToken.START_OBJECT || value == JsonToken.START_ARRAY) {
parser.skipChildren()
val end = parser.currentLocation.byteOffset.toInt()
val copy = ByteArray((end - start.byteOffset).toInt())
System.arraycopy(json, start.byteOffset.toInt(), copy, 0, copy.size)
return JsonRpcResponse(copy, null)
}
} else if (field == "error") {
val err = readError(parser)
if (err != null) {
return JsonRpcResponse(null, err)
}
} else if (value == JsonToken.START_OBJECT || value == JsonToken.START_ARRAY) {
parser.skipChildren()
val end = parser.currentLocation.byteOffset.toInt()
val copy = ByteArray((end - start.byteOffset).toInt())
System.arraycopy(json, start.byteOffset.toInt(), copy, 0, copy.size)
return JsonRpcResponse(copy, null)
}
} else if (field == "error") {
val err = readError(parser)
if (err != null) {
return JsonRpcResponse(null, err)
}
}
}
if (nullResponse != null) {
return nullResponse
if (nullResponse != null) {
return nullResponse
}
} catch (e: JsonParseException) {
log.warn("Failed to parse JSON from upstream: ${e.message}")
}
return JsonRpcResponse(null, JsonRpcResponse.ResponseError(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Invalid JSON structure"))
}