Skip to content

Commit a094f51

Browse files
[GH-50] Attached message ids to send event (#51)
Provided `publishSentEvents` option.
1 parent e8d567d commit a094f51

File tree

9 files changed

+470
-150
lines changed

9 files changed

+470
-150
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ When defined in main, they act as default values for all sessions, or as the par
3131
+ **defaultHeaders** - map of default headers, and their values which will be applied to each request (existing headers
3232
are not affected, empty by default)
3333
+ **auth** - basic authentication settings (`null` by default)
34+
+ **publishSentEvents** - enables/disables publish of "message sent" events (`true` by default)
3435

3536
### Authentication configuration
3637

@@ -152,6 +153,7 @@ spec:
152153
useTransport: false
153154
maxBatchSize: 1000
154155
maxFlushTime: 1000
156+
publishSentEvents: true
155157
defaultHeaders:
156158
x-api-key: [ 'apikeywashere' ]
157159
auth:
@@ -185,7 +187,10 @@ spec:
185187

186188
### v2.4.0
187189

190+
+ [[GH-50] Attached message ids to send event](https://github.com/th2-net/th2-conn-http-client/issues/50)
191+
+ provided `publishSentEvents` option.
188192
+ [[GH-44] Implemented multi-session feature](https://github.com/th2-net/th2-conn-http-client/issues/44)
193+
+ provided `sessions` option.
189194
+ Updated:
190195
+ th2 gradle plugin: `0.3.14` (bom: `4.14.3`)
191196
+ kotlin: `2.3.0`

src/main/kotlin/com/exactpro/th2/http/client/Application.kt

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import com.exactpro.th2.common.schema.message.MessageRouter
2626
import com.exactpro.th2.common.schema.message.QueueAttribute.RAW
2727
import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.GroupBatch
2828
import com.exactpro.th2.common.utils.event.EventBatcher
29-
import com.exactpro.th2.common.utils.event.storeEvent
3029
import com.exactpro.th2.common.utils.event.transport.toProto
3130
import com.exactpro.th2.common.utils.message.RAW_GROUP_SELECTOR
3231
import com.exactpro.th2.common.utils.message.RawMessageBatcher
@@ -42,6 +41,8 @@ import com.exactpro.th2.http.client.api.IStateManager
4241
import com.exactpro.th2.http.client.api.IStateManager.StateManagerContext
4342
import com.exactpro.th2.http.client.api.impl.BasicRequestHandler
4443
import com.exactpro.th2.http.client.api.impl.BasicStateManager
44+
import com.exactpro.th2.http.client.util.publishSentEvents
45+
import com.exactpro.th2.http.client.util.storeEvent
4546
import com.exactpro.th2.http.client.util.toPrettyString
4647
import com.exactpro.th2.http.client.util.toProtoMessage
4748
import com.exactpro.th2.http.client.util.toTransportMessage
@@ -91,29 +92,6 @@ class Application(
9192
onBatch = eventRouter::send
9293
).also { registerResource("event batcher", it::close) }
9394

94-
val onError: (Throwable) -> Unit = {
95-
eventBatcher.storeEvent(rootEventId, "Batching problem: ${it.message}", "Message batching problem", it)
96-
}
97-
98-
lateinit var transportMB: MessageBatcher
99-
lateinit var protoMB: RawMessageBatcher
100-
if (useTransport) {
101-
transportMB =
102-
MessageBatcher(
103-
maxBatchSize,
104-
maxFlushTime,
105-
book,
106-
GROUP_SELECTOR,
107-
executor,
108-
onError,
109-
transportMR::send
110-
).also { registerResource("transport message batcher", it::close) }
111-
} else {
112-
protoMB = RawMessageBatcher(maxBatchSize, maxFlushTime, RAW_GROUP_SELECTOR, executor, onError) {
113-
protoMR.send(it, RAW.value)
114-
}.also { registerResource("proto message batcher", it::close) }
115-
}
116-
11795
val aliasToService = mutableMapOf<String, Holder>()
11896
sessions.forEach { sessionAlias, sessionSettings ->
11997
val stateManager = load<IStateManager>(BasicStateManager::class.java)
@@ -138,18 +116,30 @@ class Application(
138116
val onRequest: (RawHttpRequest) -> Unit
139117
val onResponse: (RawHttpRequest, RawHttpResponse<*>) -> Unit
140118

119+
val onError: (Throwable) -> Unit = {
120+
eventBatcher.storeEvent(clientEventId, "Batching problem: ${it.message}", "Message batching problem", it)
121+
}
122+
141123
if (useTransport) {
124+
val transportMB = MessageBatcher(
125+
maxBatchSize,
126+
maxFlushTime,
127+
book,
128+
GROUP_SELECTOR,
129+
executor,
130+
onError
131+
) { batch ->
132+
transportMR.send(batch)
133+
if (!sessionSettings.publishSentEvents) return@MessageBatcher
134+
eventBatcher.publishSentEvents(clientEventId, batch)
135+
}.also { registerResource("transport message batcher $sessionAlias", it::close) }
136+
142137
onRequest = { request: RawHttpRequest ->
143-
val rawMessage = outgoingLock.withLock {
138+
outgoingLock.withLock {
144139
request.toTransportMessage(sessionAlias, outgoingSequence()).also {
145140
transportMB.onMessage(it, sessionGroup)
146141
}
147142
}
148-
eventBatcher.storeEvent(
149-
rawMessage.eventId?.toProto() ?: rootEventId,
150-
"Sent HTTP request",
151-
"Send message"
152-
)
153143
}
154144
onResponse = { request: RawHttpRequest, response: RawHttpResponse<*> ->
155145
incomingLock.withLock {
@@ -161,21 +151,27 @@ class Application(
161151
stateManager.onResponse(response)
162152
}
163153
} else {
154+
val protoMB = RawMessageBatcher(
155+
maxBatchSize,
156+
maxFlushTime,
157+
RAW_GROUP_SELECTOR,
158+
executor,
159+
onError
160+
) { batch ->
161+
protoMR.send(batch, RAW.value)
162+
if (!sessionSettings.publishSentEvents) return@RawMessageBatcher
163+
eventBatcher.publishSentEvents(clientEventId, batch)
164+
}.also { registerResource("proto message batcher $sessionAlias", it::close) }
164165
val connectionId = com.exactpro.th2.common.grpc.ConnectionID.newBuilder()
165166
.setSessionAlias(sessionAlias)
166167
.setSessionGroup(sessionGroup)
167168
.build()
168169

169170
onRequest = { request: RawHttpRequest ->
170-
val rawMessage = outgoingLock.withLock {
171+
outgoingLock.withLock {
171172
request.toProtoMessage(connectionId, outgoingSequence())
172173
.also(protoMB::onMessage)
173174
}
174-
eventBatcher.storeEvent(
175-
if (rawMessage.hasParentEventId()) rawMessage.parentEventId else rootEventId,
176-
"Sent HTTP request",
177-
"Send message"
178-
)
179175
}
180176
onResponse = { request: RawHttpRequest, response: RawHttpResponse<*> ->
181177
incomingLock.withLock {

src/main/kotlin/com/exactpro/th2/http/client/Settings.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ data class InternalSessionSettings(
4444
val validateCertificates: Boolean? = null,
4545
@param:JsonDeserialize(converter = CertificateConverter::class) val clientCertificate: X509Certificate? = null,
4646
@param:JsonDeserialize(converter = PrivateKeyConverter::class) val certificatePrivateKey: PrivateKey? = null,
47+
val publishSentEvents: Boolean? = null,
4748
)
4849

4950
data class InternalSettings(
@@ -64,6 +65,7 @@ data class InternalSettings(
6465
@Deprecated("the parameter isn't used any more", level = DeprecationLevel.ERROR) val batcherThreads: Int = 2,
6566
val maxBatchSize: Int = 1000,
6667
val maxFlushTime: Long = 1000,
68+
val publishSentEvents: Boolean = true,
6769
val sessions: Map<String, InternalSessionSettings> = emptyMap(),
6870
)
6971

@@ -79,6 +81,7 @@ data class SessionSettings(
7981
val validateCertificates: Boolean,
8082
val clientCertificate: X509Certificate?,
8183
val certificatePrivateKey: PrivateKey?,
84+
val publishSentEvents: Boolean,
8285
) {
8386
val certificate: Certificate? = clientCertificate?.run {
8487
val key = requireNotNull(certificatePrivateKey) {
@@ -140,6 +143,7 @@ fun getSettings(getFunc: (Class<InternalSettings>, ObjectMapper) -> InternalSett
140143
validateCertificates = settings.validateCertificates,
141144
clientCertificate = settings.clientCertificate,
142145
certificatePrivateKey = settings.certificatePrivateKey,
146+
publishSentEvents = settings.publishSentEvents,
143147
))
144148
} else {
145149
settings.sessions.mapValues { (key, value) ->
@@ -157,6 +161,7 @@ fun getSettings(getFunc: (Class<InternalSettings>, ObjectMapper) -> InternalSett
157161
validateCertificates = value.validateCertificates ?: settings.validateCertificates,
158162
clientCertificate = value.clientCertificate ?: settings.clientCertificate,
159163
certificatePrivateKey = value.certificatePrivateKey ?: settings.certificatePrivateKey,
164+
publishSentEvents = value.publishSentEvents ?: settings.publishSentEvents,
160165
)
161166
}
162167
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2026 Exactpro (Exactpro Systems Limited)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.exactpro.th2.http.client.util
18+
19+
import com.exactpro.th2.common.event.Event
20+
import com.exactpro.th2.common.grpc.Direction.SECOND
21+
import com.exactpro.th2.common.grpc.EventID
22+
import com.exactpro.th2.common.grpc.MessageGroupBatch
23+
import com.exactpro.th2.common.grpc.MessageID
24+
import com.exactpro.th2.common.message.direction
25+
import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.Direction.OUTGOING
26+
import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.GroupBatch
27+
import com.exactpro.th2.common.utils.event.EventBatcher
28+
import com.exactpro.th2.common.utils.event.transport.toProto
29+
import com.exactpro.th2.common.utils.message.transport.toProto
30+
31+
fun EventBatcher.publishSentEvents(rootEventId: EventID, batch: MessageGroupBatch) {
32+
publishSentEvent(hashMapOf<EventID, MutableList<MessageID>>().apply {
33+
for (group in batch.groupsList) {
34+
val message = group.messagesList[0].rawMessage
35+
if (message.direction != SECOND) continue
36+
val eventId = message.eventId ?: rootEventId
37+
this@apply.computeIfAbsent(eventId) { mutableListOf() } += message.metadata.id
38+
}
39+
})
40+
}
41+
42+
fun EventBatcher.publishSentEvents(rootEventId: EventID, batch: GroupBatch) {
43+
publishSentEvent(hashMapOf<EventID, MutableList<MessageID>>().apply {
44+
for (group in batch.groups) {
45+
val message = group.messages[0]
46+
if (message.id.direction != OUTGOING) continue
47+
val eventId = message.eventId?.toProto() ?: rootEventId
48+
this@apply.computeIfAbsent(eventId) { mutableListOf() } += message.id.toProto(batch.book, batch.sessionGroup)
49+
}
50+
})
51+
}
52+
53+
fun EventBatcher.publishSentEvent(eventMessages: Map<EventID, MutableList<MessageID>>) {
54+
for ((eventId, messageIds) in eventMessages) {
55+
storeEvent(
56+
eventId,
57+
"Sent HTTP request",
58+
"Send message",
59+
messageIds = messageIds
60+
)
61+
}
62+
}
63+
64+
fun EventBatcher.storeEvent(
65+
parentId: EventID,
66+
name: String,
67+
type: String,
68+
cause: Throwable? = null,
69+
messageIds: Iterable<MessageID> = emptySet(),
70+
): Event = Event.start().apply {
71+
endTimestamp()
72+
name(name)
73+
type(type)
74+
messageIds.forEach(this::messageID)
75+
status(if (cause != null) Event.Status.FAILED else Event.Status.PASSED)
76+
77+
var error = cause
78+
79+
while (error != null) {
80+
exception(error, true)
81+
error = error.cause
82+
}
83+
84+
onEvent(toProto(parentId))
85+
}

src/main/kotlin/com/exactpro/th2/http/client/util/MessageUtil.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2023 Exactpro (Exactpro Systems Limited)
2+
* Copyright 2020-2026 Exactpro (Exactpro Systems Limited)
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -171,6 +171,9 @@ fun TransportMessageGroup.toRequest(): RawHttpRequest = when (messages.size) {
171171
else -> error("Message group contains more than 2 messages")
172172
}
173173

174+
val RawMessage.eventId: EventID?
175+
get() = if (hasParentEventId()) parentEventId else null
176+
174177
private sealed interface IRequest {
175178
val parentEventId: EventID?
176179
val metadata: Map<String, String>

0 commit comments

Comments
 (0)