solution: access log for JSON RPC proxy
This commit is contained in:
@@ -30,6 +30,7 @@ The access log contains the JSON lines similar to:
|
|||||||
"ts":"2021-07-20T01:53:33.174645Z",
|
"ts":"2021-07-20T01:53:33.174645Z",
|
||||||
"id":"578d83db-cf53-4ef8-b73e-3f1cc0a67e96",
|
"id":"578d83db-cf53-4ef8-b73e-3f1cc0a67e96",
|
||||||
"method":"NativeCall",
|
"method":"NativeCall",
|
||||||
|
"channel":"GRPC",
|
||||||
"blockchain":"ETHEREUM",
|
"blockchain":"ETHEREUM",
|
||||||
"total":2,
|
"total":2,
|
||||||
"index":0,
|
"index":0,
|
||||||
@@ -56,6 +57,7 @@ The access log contains the JSON lines similar to:
|
|||||||
- `id` uniq id of the reply
|
- `id` uniq id of the reply
|
||||||
- `method` Dshackle method which was called (i.e., not a Blockchain API method, see `nativeCall` details)
|
- `method` Dshackle method which was called (i.e., not a Blockchain API method, see `nativeCall` details)
|
||||||
- `blockchain` blockchain code
|
- `blockchain` blockchain code
|
||||||
|
- `channel` access channel (`GRPC` for native Dshackle calls, `JSONRPC` for JSON RPC HTTP Proxy)
|
||||||
- `total` how many requests in the batch (available only for a `NativeCall` call)
|
- `total` how many requests in the batch (available only for a `NativeCall` call)
|
||||||
- `index` current index (i.e. count) of the reply to the original request
|
- `index` current index (i.e. count) of the reply to the original request
|
||||||
- `succeed` if call succeeded, in terms of Blockchain API
|
- `succeed` if call succeeded, in terms of Blockchain API
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
package io.emeraldpay.dshackle
|
package io.emeraldpay.dshackle
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.config.MainConfig
|
import io.emeraldpay.dshackle.config.MainConfig
|
||||||
import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandler
|
import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerGrpc
|
||||||
import io.grpc.*
|
import io.grpc.*
|
||||||
import io.grpc.netty.NettyServerBuilder
|
import io.grpc.netty.NettyServerBuilder
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -32,7 +32,7 @@ open class GrpcServer(
|
|||||||
@Autowired val rpcs: List<io.grpc.BindableService>,
|
@Autowired val rpcs: List<io.grpc.BindableService>,
|
||||||
@Autowired val mainConfig: MainConfig,
|
@Autowired val mainConfig: MainConfig,
|
||||||
@Autowired val tlsSetup: TlsSetup,
|
@Autowired val tlsSetup: TlsSetup,
|
||||||
@Autowired val accessHandler: AccessHandler
|
@Autowired val accessHandler: AccessHandlerGrpc
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val log = LoggerFactory.getLogger(GrpcServer::class.java)
|
private val log = LoggerFactory.getLogger(GrpcServer::class.java)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle
|
|||||||
|
|
||||||
import io.emeraldpay.dshackle.config.MainConfig
|
import io.emeraldpay.dshackle.config.MainConfig
|
||||||
import io.emeraldpay.dshackle.config.ProxyConfig
|
import io.emeraldpay.dshackle.config.ProxyConfig
|
||||||
|
import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerHttp
|
||||||
import io.emeraldpay.dshackle.proxy.ProxyServer
|
import io.emeraldpay.dshackle.proxy.ProxyServer
|
||||||
import io.emeraldpay.dshackle.proxy.ReadRpcJson
|
import io.emeraldpay.dshackle.proxy.ReadRpcJson
|
||||||
import io.emeraldpay.dshackle.proxy.WriteRpcJson
|
import io.emeraldpay.dshackle.proxy.WriteRpcJson
|
||||||
@@ -37,7 +38,8 @@ class ProxyStarter(
|
|||||||
@Autowired private val readRpcJson: ReadRpcJson,
|
@Autowired private val readRpcJson: ReadRpcJson,
|
||||||
@Autowired private val writeRpcJson: WriteRpcJson,
|
@Autowired private val writeRpcJson: WriteRpcJson,
|
||||||
@Autowired private val nativeCall: NativeCall,
|
@Autowired private val nativeCall: NativeCall,
|
||||||
@Autowired private val tlsSetup: TlsSetup
|
@Autowired private val tlsSetup: TlsSetup,
|
||||||
|
@Autowired private val accessHandlerHttp: AccessHandlerHttp
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -51,7 +53,7 @@ class ProxyStarter(
|
|||||||
log.debug("Proxy server is not configured")
|
log.debug("Proxy server is not configured")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val server = ProxyServer(config, readRpcJson, writeRpcJson, nativeCall, tlsSetup)
|
val server = ProxyServer(config, readRpcJson, writeRpcJson, nativeCall, tlsSetup, accessHandlerHttp.factory)
|
||||||
server.start()
|
server.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,20 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.monitoring.accesslog
|
package io.emeraldpay.dshackle.monitoring.accesslog
|
||||||
|
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
|
||||||
import io.emeraldpay.api.proto.Common
|
|
||||||
import io.grpc.*
|
import io.grpc.*
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class AccessHandler(
|
class AccessHandlerGrpc(
|
||||||
@Autowired private val accessLogWriter: AccessLogWriter
|
@Autowired private val accessLogWriter: AccessLogWriter
|
||||||
) : ServerInterceptor {
|
) : ServerInterceptor {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(AccessHandler::class.java)
|
private val log = LoggerFactory.getLogger(AccessHandlerGrpc::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <ReqT : Any, RespT : Any> interceptCall(
|
override fun <ReqT : Any, RespT : Any> interceptCall(
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package io.emeraldpay.dshackle.monitoring.accesslog
|
||||||
|
|
||||||
|
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||||
|
import io.emeraldpay.dshackle.config.MainConfig
|
||||||
|
import io.emeraldpay.dshackle.rpc.NativeCall
|
||||||
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import reactor.netty.http.server.HttpServerRequest
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access Log handler for JSON RPC proxy
|
||||||
|
*
|
||||||
|
* @see io.emeraldpay.dshackle.proxy.ProxyServer
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
class AccessHandlerHttp(
|
||||||
|
@Autowired private val mainConfig: MainConfig,
|
||||||
|
@Autowired accessLogWriter: AccessLogWriter
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(AccessHandlerHttp::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use factory since we need a different behaviour for situation when log is configured and when is not
|
||||||
|
*/
|
||||||
|
val factory: HandlerFactory = if (mainConfig.accessLogConfig.enabled) {
|
||||||
|
StandardFactory(accessLogWriter)
|
||||||
|
} else {
|
||||||
|
NoOpFactory()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HandlerFactory {
|
||||||
|
fun create(req: HttpServerRequest, blockchain: Chain): RequestHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoOpFactory() : HandlerFactory {
|
||||||
|
override fun create(req: HttpServerRequest, blockchain: Chain): RequestHandler {
|
||||||
|
return NoOpHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StandardFactory(val accessLogWriter: AccessLogWriter) : HandlerFactory {
|
||||||
|
override fun create(req: HttpServerRequest, blockchain: Chain): RequestHandler {
|
||||||
|
return StandardHandler(accessLogWriter, req, blockchain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestHandler {
|
||||||
|
fun close()
|
||||||
|
fun onRequest(request: BlockchainOuterClass.NativeCallRequest)
|
||||||
|
fun onResponse(callResult: NativeCall.CallResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoOpHandler : RequestHandler {
|
||||||
|
override fun close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequest(request: BlockchainOuterClass.NativeCallRequest) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(callResult: NativeCall.CallResult) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StandardHandler(
|
||||||
|
private val accessLogWriter: AccessLogWriter,
|
||||||
|
private val httpRequest: HttpServerRequest,
|
||||||
|
private val blockchain: Chain
|
||||||
|
) : RequestHandler {
|
||||||
|
|
||||||
|
private var request: BlockchainOuterClass.NativeCallRequest? = null
|
||||||
|
private val responses = ArrayList<NativeCall.CallResult>()
|
||||||
|
private val updateLock = ReentrantLock()
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
if (request == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val responseTime = Instant.now()
|
||||||
|
val builder = EventsBuilder.NativeCall()
|
||||||
|
builder.withChain(blockchain.id)
|
||||||
|
builder.start(httpRequest)
|
||||||
|
builder.onRequest(request!!)
|
||||||
|
responses
|
||||||
|
.map {
|
||||||
|
builder.onReply(it, Events.Channel.JSONRPC).also { item ->
|
||||||
|
//since for JSON RPC you get a single response then the timestamp of all items included in it must have the same timestamp
|
||||||
|
item.ts = responseTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.let(accessLogWriter::submit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequest(request: BlockchainOuterClass.NativeCallRequest) {
|
||||||
|
this.request = request
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(callResult: NativeCall.CallResult) {
|
||||||
|
updateLock.withLock {
|
||||||
|
responses.add(callResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,17 +27,22 @@ class Events {
|
|||||||
private val log = LoggerFactory.getLogger(Events::class.java)
|
private val log = LoggerFactory.getLogger(Events::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class Channel {
|
||||||
|
GRPC, JSONRPC
|
||||||
|
}
|
||||||
|
|
||||||
abstract class Base(
|
abstract class Base(
|
||||||
val id: UUID,
|
val id: UUID,
|
||||||
val method: String
|
val method: String,
|
||||||
|
val channel: Channel
|
||||||
) {
|
) {
|
||||||
val version = "accesslog/v1beta"
|
val version = "accesslog/v1beta"
|
||||||
val ts = Instant.now()
|
var ts = Instant.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ChainBase(
|
abstract class ChainBase(
|
||||||
val blockchain: Chain, method: String, id: UUID
|
val blockchain: Chain, method: String, id: UUID, channel: Channel
|
||||||
) : Base(id, method)
|
) : Base(id, method, channel)
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
class SubscribeHead(
|
class SubscribeHead(
|
||||||
@@ -46,7 +51,7 @@ class Events {
|
|||||||
val request: StreamRequestDetails,
|
val request: StreamRequestDetails,
|
||||||
// index of the current response
|
// index of the current response
|
||||||
val index: Int
|
val index: Int
|
||||||
) : ChainBase(blockchain, "SubscribeHead", id)
|
) : ChainBase(blockchain, "SubscribeHead", id, Channel.GRPC)
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
class SubscribeBalance(
|
class SubscribeBalance(
|
||||||
@@ -57,7 +62,7 @@ class Events {
|
|||||||
val addressBalance: AddressBalance,
|
val addressBalance: AddressBalance,
|
||||||
// index of the current response
|
// index of the current response
|
||||||
val index: Int
|
val index: Int
|
||||||
) : ChainBase(blockchain, if (subscribe) "SubscribeBalance" else "GetBalance", id)
|
) : ChainBase(blockchain, if (subscribe) "SubscribeBalance" else "GetBalance", id, Channel.GRPC)
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
class TxStatus(
|
class TxStatus(
|
||||||
@@ -67,7 +72,7 @@ class Events {
|
|||||||
val txStatus: TxStatusResponse,
|
val txStatus: TxStatusResponse,
|
||||||
// index of the current response
|
// index of the current response
|
||||||
val index: Int
|
val index: Int
|
||||||
) : ChainBase(blockchain, "SubscribeTxStatus", id)
|
) : ChainBase(blockchain, "SubscribeTxStatus", id, Channel.GRPC)
|
||||||
|
|
||||||
data class TxStatusRequest(
|
data class TxStatusRequest(
|
||||||
val txId: String
|
val txId: String
|
||||||
@@ -79,7 +84,7 @@ class Events {
|
|||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
class NativeCall(
|
class NativeCall(
|
||||||
blockchain: Chain, id: UUID,
|
blockchain: Chain, id: UUID, channel: Channel,
|
||||||
|
|
||||||
// info about the initial request, that may include several native calls
|
// info about the initial request, that may include several native calls
|
||||||
val request: StreamRequestDetails,
|
val request: StreamRequestDetails,
|
||||||
@@ -95,19 +100,19 @@ class Events {
|
|||||||
val rpcError: Int? = null,
|
val rpcError: Int? = null,
|
||||||
val payloadSizeBytes: Long,
|
val payloadSizeBytes: Long,
|
||||||
val nativeCall: NativeCallItemDetails
|
val nativeCall: NativeCallItemDetails
|
||||||
) : ChainBase(blockchain, "NativeCall", id)
|
) : ChainBase(blockchain, "NativeCall", id, channel)
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
class Describe(
|
class Describe(
|
||||||
id: UUID,
|
id: UUID,
|
||||||
val request: StreamRequestDetails
|
val request: StreamRequestDetails
|
||||||
) : Base(id, "Describe")
|
) : Base(id, "Describe", Channel.GRPC)
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
class Status(
|
class Status(
|
||||||
blockchain: Chain, id: UUID,
|
blockchain: Chain, id: UUID,
|
||||||
val request: StreamRequestDetails
|
val request: StreamRequestDetails
|
||||||
) : ChainBase(blockchain, "Status", id)
|
) : ChainBase(blockchain, "Status", id, Channel.GRPC)
|
||||||
|
|
||||||
data class StreamRequestDetails(
|
data class StreamRequestDetails(
|
||||||
val id: UUID,
|
val id: UUID,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import io.grpc.Grpc
|
|||||||
import io.grpc.Metadata
|
import io.grpc.Metadata
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import reactor.netty.http.server.HttpServerRequest
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -34,17 +35,25 @@ class EventsBuilder {
|
|||||||
private val log = LoggerFactory.getLogger(EventsBuilder::class.java)
|
private val log = LoggerFactory.getLogger(EventsBuilder::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StartingRequest {
|
interface StartingHttp2Request {
|
||||||
fun start(metadata: Metadata, attributes: Attributes)
|
fun start(metadata: Metadata, attributes: Attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RequestReply<E, Req, Resp> : StartingRequest {
|
interface StartingHttp1Request {
|
||||||
|
fun start(request: HttpServerRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestReply<E, Req, Resp> : StartingHttp2Request {
|
||||||
fun onRequest(msg: Req)
|
fun onRequest(msg: Req)
|
||||||
fun onReply(msg: Resp): E
|
fun onReply(msg: Resp): E
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Base<T>() : StartingRequest {
|
abstract class Base<T>() : StartingHttp2Request, StartingHttp1Request {
|
||||||
companion object {
|
companion object {
|
||||||
|
private val remoteIpHeaders = listOf(
|
||||||
|
"x-real-ip",
|
||||||
|
"x-forwarded-for"
|
||||||
|
)
|
||||||
private val remoteIpKeys = listOf(
|
private val remoteIpKeys = listOf(
|
||||||
Metadata.Key.of("x-real-ip", Metadata.ASCII_STRING_MARSHALLER),
|
Metadata.Key.of("x-real-ip", Metadata.ASCII_STRING_MARSHALLER),
|
||||||
Metadata.Key.of("x-forwarded-for", Metadata.ASCII_STRING_MARSHALLER)
|
Metadata.Key.of("x-forwarded-for", Metadata.ASCII_STRING_MARSHALLER)
|
||||||
@@ -120,6 +129,31 @@ class EventsBuilder {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun start(request: HttpServerRequest) {
|
||||||
|
val headers = request.requestHeaders()
|
||||||
|
val userAgent = headers.get("user-agent")
|
||||||
|
?.let(this@Base::clean)
|
||||||
|
?: ""
|
||||||
|
val ips = ArrayList<InetAddress>()
|
||||||
|
remoteIpHeaders.forEach { key ->
|
||||||
|
headers.get(key)?.let {
|
||||||
|
it.trim().ifEmpty { null }
|
||||||
|
?.let(this@Base::toInetAddress)
|
||||||
|
?.let(ips::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.remoteAddress()?.let { addr ->
|
||||||
|
ips.add(addr.address)
|
||||||
|
}
|
||||||
|
val ip = findBestIp(ips)?.hostAddress ?: ""
|
||||||
|
this.requestDetails = this.requestDetails
|
||||||
|
.copy(remote = Events.Remote(
|
||||||
|
ips = ips.map { it.hostAddress },
|
||||||
|
ip = ip,
|
||||||
|
userAgent = userAgent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fun withChain(chain: Int): T {
|
fun withChain(chain: Int): T {
|
||||||
this.chainId = chain
|
this.chainId = chain
|
||||||
this.chain = Chain.byId(chainId)
|
this.chain = Chain.byId(chainId)
|
||||||
@@ -237,10 +271,26 @@ class EventsBuilder {
|
|||||||
blockchain = chain,
|
blockchain = chain,
|
||||||
nativeCall = item,
|
nativeCall = item,
|
||||||
payloadSizeBytes = item.payloadSizeBytes,
|
payloadSizeBytes = item.payloadSizeBytes,
|
||||||
id = UUID.randomUUID()
|
id = UUID.randomUUID(),
|
||||||
|
channel = Events.Channel.GRPC
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onReply(reply: io.emeraldpay.dshackle.rpc.NativeCall.CallResult,
|
||||||
|
channel: Events.Channel): Events.NativeCall {
|
||||||
|
val item = items.find { it.id == reply.id }!!
|
||||||
|
return Events.NativeCall(
|
||||||
|
request = requestDetails,
|
||||||
|
total = items.size,
|
||||||
|
index = index++,
|
||||||
|
succeed = !reply.isError(),
|
||||||
|
blockchain = chain,
|
||||||
|
nativeCall = item,
|
||||||
|
payloadSizeBytes = item.payloadSizeBytes,
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
channel = channel
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Describe :
|
class Describe :
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import io.emeraldpay.dshackle.ChainValue
|
|||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.TlsSetup
|
import io.emeraldpay.dshackle.TlsSetup
|
||||||
import io.emeraldpay.dshackle.config.ProxyConfig
|
import io.emeraldpay.dshackle.config.ProxyConfig
|
||||||
|
import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerHttp
|
||||||
import io.emeraldpay.dshackle.rpc.NativeCall
|
import io.emeraldpay.dshackle.rpc.NativeCall
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
@@ -33,7 +34,6 @@ import io.netty.buffer.ByteBuf
|
|||||||
import io.netty.buffer.Unpooled
|
import io.netty.buffer.Unpooled
|
||||||
import io.netty.channel.ChannelHandler
|
import io.netty.channel.ChannelHandler
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.ChannelOption
|
|
||||||
import org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.http.HttpHeaders
|
import org.springframework.http.HttpHeaders
|
||||||
@@ -55,7 +55,8 @@ class ProxyServer(
|
|||||||
private val readRpcJson: ReadRpcJson,
|
private val readRpcJson: ReadRpcJson,
|
||||||
private val writeRpcJson: WriteRpcJson,
|
private val writeRpcJson: WriteRpcJson,
|
||||||
private val nativeCall: NativeCall,
|
private val nativeCall: NativeCall,
|
||||||
private val tlsSetup: TlsSetup
|
private val tlsSetup: TlsSetup,
|
||||||
|
private val accessHandler: AccessHandlerHttp.HandlerFactory
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -113,13 +114,15 @@ class ProxyServer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun execute(chain: Common.ChainRef, call: ProxyCall): Publisher<String> {
|
fun execute(chain: Common.ChainRef, call: ProxyCall, handler: AccessHandlerHttp.RequestHandler): Publisher<String> {
|
||||||
val request = BlockchainOuterClass.NativeCallRequest.newBuilder()
|
val request = BlockchainOuterClass.NativeCallRequest.newBuilder()
|
||||||
.setChain(chain)
|
.setChain(chain)
|
||||||
.addAllItems(call.items)
|
.addAllItems(call.items)
|
||||||
.build()
|
.build()
|
||||||
|
handler.onRequest(request)
|
||||||
val jsons = nativeCall
|
val jsons = nativeCall
|
||||||
.nativeCallResult(Mono.just(request))
|
.nativeCallResult(Mono.just(request))
|
||||||
|
.doOnNext { handler.onResponse(it) }
|
||||||
.transform(writeRpcJson.toJsons(call))
|
.transform(writeRpcJson.toJsons(call))
|
||||||
return if (call.type == ProxyCall.RpcType.SINGLE) {
|
return if (call.type == ProxyCall.RpcType.SINGLE) {
|
||||||
jsons.next()
|
jsons.next()
|
||||||
@@ -128,13 +131,13 @@ class ProxyServer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processRequest(chain: Common.ChainRef, request: Mono<ByteArray>): Flux<ByteBuf> {
|
fun processRequest(chain: Common.ChainRef, request: Mono<ByteArray>, handler: AccessHandlerHttp.RequestHandler): Flux<ByteBuf> {
|
||||||
val metrics = chainMetrics.get(chain)
|
val metrics = chainMetrics.get(chain)
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
metrics.requestMetric.increment()
|
metrics.requestMetric.increment()
|
||||||
return request
|
return request
|
||||||
.map(readRpcJson)
|
.map(readRpcJson)
|
||||||
.flatMapMany { call -> execute(chain, call) }
|
.flatMapMany { call -> execute(chain, call, handler) }
|
||||||
.doOnNext {
|
.doOnNext {
|
||||||
metrics.callMetric.record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS)
|
metrics.callMetric.record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS)
|
||||||
}
|
}
|
||||||
@@ -153,10 +156,14 @@ class ProxyServer(
|
|||||||
fun proxy(routeConfig: ProxyConfig.Route): BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> {
|
fun proxy(routeConfig: ProxyConfig.Route): BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> {
|
||||||
val chain = Common.ChainRef.forNumber(routeConfig.blockchain.id)
|
val chain = Common.ChainRef.forNumber(routeConfig.blockchain.id)
|
||||||
return BiFunction { req, resp ->
|
return BiFunction { req, resp ->
|
||||||
|
// handle access events
|
||||||
|
val eventHandler = accessHandler.create(req, routeConfig.blockchain)
|
||||||
val request = req.receive()
|
val request = req.receive()
|
||||||
.aggregate()
|
.aggregate()
|
||||||
.asByteArray()
|
.asByteArray()
|
||||||
val results = processRequest(chain, request)
|
val results = processRequest(chain, request, eventHandler)
|
||||||
|
// make sure that the access log handler is closed at the end, so it can render the logs
|
||||||
|
.doFinally { eventHandler.close() }
|
||||||
resp.addHeader(HttpHeaders.CONTENT_TYPE, "application/json")
|
resp.addHeader(HttpHeaders.CONTENT_TYPE, "application/json")
|
||||||
.send(results)
|
.send(results)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class EventsBaseBuilderSpec extends Specification {
|
|||||||
Events.StreamRequestDetails request
|
Events.StreamRequestDetails request
|
||||||
|
|
||||||
TestEvent(Events.StreamRequestDetails request) {
|
TestEvent(Events.StreamRequestDetails request) {
|
||||||
super(UUID.randomUUID(), "TEST")
|
super(UUID.randomUUID(), "TEST", Events.Channel.GRPC)
|
||||||
this.request = request
|
this.request = request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,12 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.proxy
|
package io.emeraldpay.dshackle.proxy
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||||
import io.emeraldpay.api.proto.Common
|
import io.emeraldpay.api.proto.Common
|
||||||
import io.emeraldpay.dshackle.TlsSetup
|
import io.emeraldpay.dshackle.TlsSetup
|
||||||
import io.emeraldpay.dshackle.config.ProxyConfig
|
import io.emeraldpay.dshackle.config.ProxyConfig
|
||||||
|
import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerHttp
|
||||||
import io.emeraldpay.dshackle.rpc.NativeCall
|
import io.emeraldpay.dshackle.rpc.NativeCall
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
@@ -48,7 +50,8 @@ class ProxyServerSpec extends Specification {
|
|||||||
new ReadRpcJson(),
|
new ReadRpcJson(),
|
||||||
writeRpcJson,
|
writeRpcJson,
|
||||||
nativeCall,
|
nativeCall,
|
||||||
new TlsSetup(TestingCommons.fileResolver())
|
new TlsSetup(TestingCommons.fileResolver()),
|
||||||
|
new AccessHandlerHttp.NoOpFactory()
|
||||||
)
|
)
|
||||||
|
|
||||||
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
|
def call = new ProxyCall(ProxyCall.RpcType.SINGLE)
|
||||||
@@ -59,7 +62,7 @@ class ProxyServerSpec extends Specification {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
when:
|
when:
|
||||||
def act = server.execute(Common.ChainRef.CHAIN_ETHEREUM, call)
|
def act = server.execute(Common.ChainRef.CHAIN_ETHEREUM, call, new AccessHandlerHttp.NoOpHandler())
|
||||||
|
|
||||||
then:
|
then:
|
||||||
1 * nativeCall.nativeCallResult(_) >> Flux.just(new NativeCall.CallResult(1, "".bytes, null))
|
1 * nativeCall.nativeCallResult(_) >> Flux.just(new NativeCall.CallResult(1, "".bytes, null))
|
||||||
@@ -77,10 +80,11 @@ class ProxyServerSpec extends Specification {
|
|||||||
def server = new ProxyServer(
|
def server = new ProxyServer(
|
||||||
Stub(ProxyConfig),
|
Stub(ProxyConfig),
|
||||||
read,
|
read,
|
||||||
Stub(WriteRpcJson), Stub(NativeCall), Stub(TlsSetup)
|
Stub(WriteRpcJson), Stub(NativeCall), Stub(TlsSetup),
|
||||||
|
new AccessHandlerHttp.NoOpFactory()
|
||||||
)
|
)
|
||||||
when:
|
when:
|
||||||
def act = server.processRequest(Common.ChainRef.CHAIN_ETHEREUM, Mono.just("".bytes))
|
def act = server.processRequest(Common.ChainRef.CHAIN_ETHEREUM, Mono.just("".bytes), new AccessHandlerHttp.NoOpHandler())
|
||||||
.map { new String(it.array()) }
|
.map { new String(it.array()) }
|
||||||
then:
|
then:
|
||||||
StepVerifier.create(act)
|
StepVerifier.create(act)
|
||||||
@@ -88,4 +92,44 @@ class ProxyServerSpec extends Specification {
|
|||||||
.expectComplete()
|
.expectComplete()
|
||||||
.verify(Duration.ofSeconds(1))
|
.verify(Duration.ofSeconds(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "Calls access log handler"() {
|
||||||
|
setup:
|
||||||
|
def reqItem = BlockchainOuterClass.NativeCallItem.newBuilder()
|
||||||
|
.setId(1)
|
||||||
|
.setMethod("test_test")
|
||||||
|
.setPayload(ByteString.copyFromUtf8("[]"))
|
||||||
|
.build()
|
||||||
|
def respItem = new NativeCall.CallResult(1, "100".bytes, null)
|
||||||
|
def req = BlockchainOuterClass.NativeCallRequest.newBuilder()
|
||||||
|
.setChain(Common.ChainRef.CHAIN_ETHEREUM)
|
||||||
|
.addItems(reqItem)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
|
||||||
|
ReadRpcJson read = Mock(ReadRpcJson) {
|
||||||
|
1 * apply(_) >> new ProxyCall(ProxyCall.RpcType.SINGLE).tap { it.items.add(reqItem) }
|
||||||
|
}
|
||||||
|
NativeCall nativeCall = Mock(NativeCall) {
|
||||||
|
1 * nativeCallResult(_) >> Flux.fromIterable([respItem])
|
||||||
|
}
|
||||||
|
def handler = Mock(AccessHandlerHttp.RequestHandler.class)
|
||||||
|
|
||||||
|
def server = new ProxyServer(
|
||||||
|
Stub(ProxyConfig),
|
||||||
|
read,
|
||||||
|
new WriteRpcJson(),
|
||||||
|
nativeCall,
|
||||||
|
Stub(TlsSetup),
|
||||||
|
new AccessHandlerHttp.NoOpFactory()
|
||||||
|
)
|
||||||
|
|
||||||
|
when:
|
||||||
|
server.processRequest(Common.ChainRef.CHAIN_ETHEREUM, Mono.just("".bytes), handler)
|
||||||
|
.blockLast()
|
||||||
|
|
||||||
|
then:
|
||||||
|
1 * handler.onRequest(req)
|
||||||
|
1 * handler.onResponse(respItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user