fix streaming when response and error present in response (#469)
This commit is contained in:
@@ -37,7 +37,10 @@ class JsonRpcStreamParser(
|
||||
private const val QUOTE: Byte = '"'.code.toByte()
|
||||
}
|
||||
|
||||
fun streamParse(statusCode: Int, response: Flux<ByteArray>): Mono<out Response> {
|
||||
fun streamParse(
|
||||
statusCode: Int,
|
||||
response: Flux<ByteArray>,
|
||||
): Mono<out Response> {
|
||||
val firstPartSize = AtomicInteger()
|
||||
return response.bufferUntil {
|
||||
if (firstPartSize.get() > firstChunkMaxSize) {
|
||||
@@ -107,7 +110,10 @@ class JsonRpcStreamParser(
|
||||
}
|
||||
}
|
||||
|
||||
private fun aggregateResponse(response: Flux<ByteArray>, statusCode: Int): Mono<AggregateResponse> {
|
||||
private fun aggregateResponse(
|
||||
response: Flux<ByteArray>,
|
||||
statusCode: Int,
|
||||
): Mono<AggregateResponse> {
|
||||
return ByteBufFlux.fromInbound(response).aggregate().asByteArray()
|
||||
.map { AggregateResponse(it, statusCode) }
|
||||
}
|
||||
@@ -161,10 +167,10 @@ class JsonRpcStreamParser(
|
||||
endStream: AtomicBoolean,
|
||||
whatCount: AtomicReference<Count>,
|
||||
): SingleResponse? {
|
||||
var response: SingleResponse? = null
|
||||
try {
|
||||
jsonFactory.createParser(firstBytes).use { parser ->
|
||||
while (true) {
|
||||
parser.nextToken()
|
||||
while (parser.nextToken() != null) {
|
||||
if (firstBytes.size == parser.currentLocation.byteOffset.toInt()) {
|
||||
break
|
||||
}
|
||||
@@ -172,51 +178,56 @@ class JsonRpcStreamParser(
|
||||
if (parser.currentName == "result") {
|
||||
val token = parser.nextToken()
|
||||
val tokenStart = parser.tokenLocation.byteOffset.toInt()
|
||||
return if (token.isScalarValue) {
|
||||
if (token.isScalarValue) {
|
||||
val count = CountQuotesAndSlashes(AtomicInteger(1))
|
||||
whatCount.set(count)
|
||||
SingleResponse(
|
||||
processScalarValue(parser, tokenStart, firstBytes, count, endStream),
|
||||
null,
|
||||
)
|
||||
response =
|
||||
SingleResponse(
|
||||
processScalarValue(parser, tokenStart, firstBytes, count, endStream),
|
||||
null,
|
||||
).merge(response)
|
||||
} else {
|
||||
when (token) {
|
||||
JsonToken.START_OBJECT -> {
|
||||
val count = CountObjectBrackets(
|
||||
AtomicInteger(1),
|
||||
CountQuotesAndSlashes(AtomicInteger(0)),
|
||||
)
|
||||
val count =
|
||||
CountObjectBrackets(
|
||||
AtomicInteger(1),
|
||||
CountQuotesAndSlashes(AtomicInteger(0)),
|
||||
)
|
||||
whatCount.set(count)
|
||||
SingleResponse(
|
||||
processAndCountBrackets(
|
||||
tokenStart,
|
||||
firstBytes,
|
||||
count,
|
||||
endStream,
|
||||
OBJECT_OPEN_BRACKET,
|
||||
OBJECT_CLOSE_BRACKET,
|
||||
),
|
||||
null,
|
||||
)
|
||||
response =
|
||||
SingleResponse(
|
||||
processAndCountBrackets(
|
||||
tokenStart,
|
||||
firstBytes,
|
||||
count,
|
||||
endStream,
|
||||
OBJECT_OPEN_BRACKET,
|
||||
OBJECT_CLOSE_BRACKET,
|
||||
),
|
||||
null,
|
||||
).merge(response)
|
||||
}
|
||||
|
||||
JsonToken.START_ARRAY -> {
|
||||
val count = CountArrayBrackets(
|
||||
AtomicInteger(1),
|
||||
CountQuotesAndSlashes(AtomicInteger(0)),
|
||||
)
|
||||
val count =
|
||||
CountArrayBrackets(
|
||||
AtomicInteger(1),
|
||||
CountQuotesAndSlashes(AtomicInteger(0)),
|
||||
)
|
||||
whatCount.set(count)
|
||||
SingleResponse(
|
||||
processAndCountBrackets(
|
||||
tokenStart,
|
||||
firstBytes,
|
||||
count,
|
||||
endStream,
|
||||
ARRAY_OPEN_BRACKET,
|
||||
ARRAY_CLOSE_BRACKET,
|
||||
),
|
||||
null,
|
||||
)
|
||||
response =
|
||||
SingleResponse(
|
||||
processAndCountBrackets(
|
||||
tokenStart,
|
||||
firstBytes,
|
||||
count,
|
||||
endStream,
|
||||
ARRAY_OPEN_BRACKET,
|
||||
ARRAY_CLOSE_BRACKET,
|
||||
),
|
||||
null,
|
||||
).merge(response)
|
||||
}
|
||||
|
||||
else -> {
|
||||
@@ -225,15 +236,15 @@ class JsonRpcStreamParser(
|
||||
}
|
||||
}
|
||||
} else if (parser.currentName == "error") {
|
||||
return SingleResponse(null, responseRpcParser.readError(parser))
|
||||
return SingleResponse(null, responseRpcParser.readError(parser)).merge(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
return response
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.warn("Streaming parsing exception: {}", e.message)
|
||||
return null
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,21 @@ data class SingleResponse(
|
||||
|
||||
fun noResponse() = result == null && error == null
|
||||
|
||||
fun merge(other: SingleResponse?): SingleResponse {
|
||||
if (other == null) {
|
||||
return this
|
||||
}
|
||||
var newResult: ByteArray? = result
|
||||
var newError: ChainCallError? = error
|
||||
if (newResult == null && other.result != null) {
|
||||
newResult = other.result
|
||||
}
|
||||
if (newError == null && other.error != null) {
|
||||
newError = other.error
|
||||
}
|
||||
return SingleResponse(newResult, newError)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is SingleResponse) return false
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.emeraldpay.dshackle.upstream.rpcclient.stream
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.ChainCallError
|
||||
import io.emeraldpay.dshackle.upstream.stream.AggregateResponse
|
||||
import io.emeraldpay.dshackle.upstream.stream.Chunk
|
||||
import io.emeraldpay.dshackle.upstream.stream.SingleResponse
|
||||
@@ -71,6 +72,28 @@ class JsonRpcStreamParserTest {
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if first part has result field and error then single response`() {
|
||||
val statusCode = 200
|
||||
val response =
|
||||
@Suppress("ktlint:standard:max-line-length")
|
||||
"{\"id\": 2,\"result\": null, \"error\":{\"code\":-32000,\"message\":\"tracing failed: fee cap less than block base fee\"}}".toByteArray()
|
||||
val stream: Flux<ByteArray> = Flux.just(response)
|
||||
|
||||
StepVerifier.create(streamParser.streamParse(statusCode, stream))
|
||||
.expectNext(
|
||||
SingleResponse(
|
||||
"null".toByteArray(),
|
||||
ChainCallError(
|
||||
-32000,
|
||||
"tracing failed: fee cap less than block base fee",
|
||||
),
|
||||
),
|
||||
)
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("dataStream")
|
||||
fun `if big result then stream response`(
|
||||
|
||||
Reference in New Issue
Block a user