├── .gitignore ├── LICENSE ├── README.md ├── Taskfile.yml ├── client.go ├── command.go ├── commands_client.go ├── common.go ├── common └── channel_stats.go ├── event.go ├── event_store.go ├── events_client.go ├── events_store_client.go ├── examples ├── README.md ├── cq │ ├── create │ │ └── main.go │ ├── delete │ │ └── main.go │ ├── functions │ │ └── main.go │ └── list │ │ └── main.go ├── pubsub │ ├── create │ │ └── main.go │ ├── delete │ │ └── main.go │ ├── functions │ │ └── main.go │ └── list │ │ └── main.go └── queues │ ├── create │ └── main.go │ ├── delete │ └── main.go │ ├── list │ └── main.go │ └── operations │ └── main.go ├── go.mod ├── go.sum ├── grpc.go ├── options.go ├── pkg └── uuid │ └── uuid.go ├── queries_client.go ├── query.go ├── queue.go ├── queues_client.go ├── queues_stream ├── ack_all_messages.go ├── client.go ├── client_test.go ├── downstream.go ├── grpc.go ├── message.go ├── options.go ├── poll_request.go ├── poll_response.go ├── queue_info.go ├── response_handler.go ├── send_result.go ├── subscribe_request.go ├── subscribe_response.go └── upstream.go ├── response.go ├── rest.go ├── trace.go └── transport.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | *.idea 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | # github.com/go-task/task 2 | 3 | version: '3' 4 | 5 | vars: 6 | BINARY_NAME: kubemq-go 7 | VERSION: v1.10.0 8 | 9 | tasks: 10 | check_update: 11 | cmds: 12 | - go list -u -m -json -mod=mod all | go-mod-outdated -update -direct 13 | lint: 14 | cmds: 15 | - golangci-lint run --disable gocritic --enable misspell 16 | commit-modifed: 17 | cmds: 18 | - git add -A 19 | - git commit -a -m "release {{.VERSION}}" 20 | - git push origin master 21 | tag: 22 | cmds: 23 | - git tag -a {{.VERSION}} -m {{.VERSION}} 24 | - git push origin master --tags 25 | release: 26 | cmds: 27 | - task: lint 28 | - task: commit-modifed 29 | - task: tag 30 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | pb "github.com/kubemq-io/protobuf/go" 7 | "time" 8 | ) 9 | 10 | const ( 11 | defaultRequestTimeout = time.Second * 5 12 | ) 13 | 14 | var ( 15 | ErrNoTransportDefined = errors.New("no transport layer defined, create object with client instance") 16 | ErrNoTransportConnection = errors.New("no transport layer established, aborting") 17 | ) 18 | 19 | type ServerInfo struct { 20 | Host string 21 | Version string 22 | ServerStartTime int64 23 | ServerUpTimeSeconds int64 24 | } 25 | 26 | type Client struct { 27 | opts *Options 28 | transport Transport 29 | ServerInfo *ServerInfo 30 | singleStreamQueueMutex chan bool 31 | // currentSQM *StreamQueueMessage 32 | } 33 | 34 | // NewClient - create client instance to be use to communicate with KubeMQ server 35 | func NewClient(ctx context.Context, op ...Option) (*Client, error) { 36 | opts := GetDefaultOptions() 37 | for _, o := range op { 38 | o.apply(opts) 39 | } 40 | client := &Client{ 41 | opts: opts, 42 | } 43 | 44 | err := opts.Validate() 45 | if err != nil { 46 | return nil, err 47 | } 48 | switch opts.transportType { 49 | case TransportTypeGRPC: 50 | client.transport, client.ServerInfo, err = newGRPCTransport(ctx, opts) 51 | case TransportTypeRest: 52 | client.transport, client.ServerInfo, err = newRestTransport(ctx, opts) 53 | } 54 | if err != nil { 55 | return nil, err 56 | } 57 | if client.transport == nil { 58 | return nil, ErrNoTransportConnection 59 | } 60 | client.singleStreamQueueMutex = make(chan bool, 1) 61 | return client, nil 62 | } 63 | 64 | // Close - closing client connection. any on going transactions will be aborted 65 | func (c *Client) Close() error { 66 | if c.transport != nil { 67 | return c.transport.Close() 68 | } 69 | return nil 70 | } 71 | 72 | // NewEvent - create an empty event 73 | func (c *Client) NewEvent() *Event { 74 | return c.E() 75 | } 76 | 77 | // E - create an empty event object 78 | func (c *Client) E() *Event { 79 | return &Event{ 80 | Id: "", 81 | Channel: c.opts.defaultChannel, 82 | Metadata: "", 83 | Body: nil, 84 | ClientId: c.opts.clientId, 85 | Tags: map[string]string{}, 86 | transport: c.transport, 87 | } 88 | } 89 | 90 | // NewEventStore- create an empty event store 91 | func (c *Client) NewEventStore() *EventStore { 92 | return c.ES() 93 | } 94 | 95 | // ES - create an empty event store object 96 | func (c *Client) ES() *EventStore { 97 | return &EventStore{ 98 | Id: "", 99 | Channel: c.opts.defaultChannel, 100 | Metadata: "", 101 | Body: nil, 102 | ClientId: c.opts.clientId, 103 | Tags: map[string]string{}, 104 | transport: c.transport, 105 | } 106 | } 107 | 108 | // StreamEvents - send stream of events in a single call 109 | func (c *Client) StreamEvents(ctx context.Context, eventsCh chan *Event, errCh chan error) { 110 | c.transport.StreamEvents(ctx, eventsCh, errCh) 111 | } 112 | 113 | // StreamEventsStore - send stream of events store in a single call 114 | func (c *Client) StreamEventsStore(ctx context.Context, eventsCh chan *EventStore, eventsResultCh chan *EventStoreResult, errCh chan error) { 115 | c.transport.StreamEventsStore(ctx, eventsCh, eventsResultCh, errCh) 116 | } 117 | 118 | // NewCommand - create an empty command 119 | func (c *Client) NewCommand() *Command { 120 | return c.C() 121 | } 122 | 123 | // C - create an empty command object 124 | func (c *Client) C() *Command { 125 | return &Command{ 126 | Id: "", 127 | Channel: c.opts.defaultChannel, 128 | Metadata: "", 129 | Body: nil, 130 | Timeout: defaultRequestTimeout, 131 | ClientId: c.opts.clientId, 132 | Tags: map[string]string{}, 133 | transport: c.transport, 134 | trace: nil, 135 | } 136 | } 137 | 138 | // NewQuery - create an empty query 139 | func (c *Client) NewQuery() *Query { 140 | return c.Q() 141 | } 142 | 143 | // Q - create an empty query object 144 | func (c *Client) Q() *Query { 145 | return &Query{ 146 | Id: "", 147 | Channel: c.opts.defaultChannel, 148 | Metadata: "", 149 | Body: nil, 150 | Timeout: defaultRequestTimeout, 151 | ClientId: c.opts.clientId, 152 | CacheKey: "", 153 | CacheTTL: c.opts.defaultCacheTTL, 154 | Tags: map[string]string{}, 155 | transport: c.transport, 156 | trace: nil, 157 | } 158 | } 159 | 160 | // NewResponse - create an empty response 161 | func (c *Client) NewResponse() *Response { 162 | return c.R() 163 | } 164 | 165 | // R - create an empty response object for command or query responses 166 | func (c *Client) R() *Response { 167 | return &Response{ 168 | RequestId: "", 169 | ResponseTo: "", 170 | Metadata: "", 171 | Body: nil, 172 | ClientId: c.opts.clientId, 173 | ExecutedAt: time.Time{}, 174 | Err: nil, 175 | Tags: map[string]string{}, 176 | transport: c.transport, 177 | trace: nil, 178 | } 179 | } 180 | 181 | // SubscribeToEvents - subscribe to events by channel and group. return channel of events or en error 182 | func (c *Client) SubscribeToEvents(ctx context.Context, channel, group string, errCh chan error) (<-chan *Event, error) { 183 | return c.transport.SubscribeToEvents(ctx, &EventsSubscription{ 184 | Channel: channel, 185 | Group: group, 186 | ClientId: c.opts.clientId, 187 | }, errCh) 188 | } 189 | 190 | // SubscribeToEvents - subscribe to events by channel and group. return channel of events or en error 191 | func (c *Client) SubscribeToEventsWithRequest(ctx context.Context, request *EventsSubscription, errCh chan error) (<-chan *Event, error) { 192 | return c.transport.SubscribeToEvents(ctx, request, errCh) 193 | } 194 | 195 | // SubscribeToEventsStore - subscribe to events store by channel and group with subscription option. return channel of events or en error 196 | func (c *Client) SubscribeToEventsStore(ctx context.Context, channel, group string, errCh chan error, opt SubscriptionOption) (<-chan *EventStoreReceive, error) { 197 | return c.transport.SubscribeToEventsStore(ctx, &EventsStoreSubscription{ 198 | Channel: channel, 199 | Group: group, 200 | ClientId: c.opts.clientId, 201 | SubscriptionType: opt, 202 | }, errCh) 203 | } 204 | 205 | // SubscribeToEventsStoreWithRequest - subscribe to events store by channel and group with subscription option. return channel of events or en error 206 | func (c *Client) SubscribeToEventsStoreWithRequest(ctx context.Context, request *EventsStoreSubscription, errCh chan error) (<-chan *EventStoreReceive, error) { 207 | return c.transport.SubscribeToEventsStore(ctx, request, errCh) 208 | } 209 | 210 | // SubscribeToCommands - subscribe to commands requests by channel and group. return channel of CommandReceived or en error 211 | func (c *Client) SubscribeToCommands(ctx context.Context, channel, group string, errCh chan error) (<-chan *CommandReceive, error) { 212 | return c.transport.SubscribeToCommands(ctx, &CommandsSubscription{ 213 | Channel: channel, 214 | Group: group, 215 | ClientId: c.opts.clientId, 216 | }, errCh) 217 | } 218 | 219 | // SubscribeToCommands - subscribe to commands requests by channel and group. return channel of CommandReceived or en error 220 | func (c *Client) SubscribeToCommandsWithRequest(ctx context.Context, request *CommandsSubscription, errCh chan error) (<-chan *CommandReceive, error) { 221 | return c.transport.SubscribeToCommands(ctx, request, errCh) 222 | } 223 | 224 | // SubscribeToQueries - subscribe to queries requests by channel and group. return channel of QueryReceived or en error 225 | func (c *Client) SubscribeToQueriesWithRequest(ctx context.Context, request *QueriesSubscription, errCh chan error) (<-chan *QueryReceive, error) { 226 | return c.transport.SubscribeToQueries(ctx, request, errCh) 227 | } 228 | 229 | // SubscribeToQueries - subscribe to queries requests by channel and group. return channel of QueryReceived or en error 230 | func (c *Client) SubscribeToQueries(ctx context.Context, channel, group string, errCh chan error) (<-chan *QueryReceive, error) { 231 | return c.transport.SubscribeToQueries(ctx, &QueriesSubscription{ 232 | Channel: channel, 233 | Group: group, 234 | ClientId: c.opts.clientId, 235 | }, errCh) 236 | } 237 | 238 | // NewQueueMessage - create an empty queue messages 239 | func (c *Client) NewQueueMessage() *QueueMessage { 240 | return c.QM() 241 | } 242 | 243 | // QM - create an empty queue message object 244 | func (c *Client) QM() *QueueMessage { 245 | return &QueueMessage{ 246 | QueueMessage: &pb.QueueMessage{ 247 | MessageID: "", 248 | ClientID: c.opts.clientId, 249 | Channel: "", 250 | Metadata: "", 251 | Body: nil, 252 | Tags: map[string]string{}, 253 | Attributes: nil, 254 | Policy: &pb.QueueMessagePolicy{ 255 | ExpirationSeconds: 0, 256 | DelaySeconds: 0, 257 | MaxReceiveCount: 0, 258 | MaxReceiveQueue: "", 259 | }, 260 | }, 261 | 262 | transport: c.transport, 263 | trace: nil, 264 | } 265 | } 266 | 267 | // NewQueueMessages - create an empty queue messages array 268 | func (c *Client) NewQueueMessages() *QueueMessages { 269 | return c.QMB() 270 | } 271 | 272 | // QMB - create an empty queue message array object 273 | func (c *Client) QMB() *QueueMessages { 274 | return &QueueMessages{ 275 | Messages: []*QueueMessage{}, 276 | transport: c.transport, 277 | } 278 | } 279 | 280 | // SendQueueMessage - send single queue message 281 | func (c *Client) SendQueueMessage(ctx context.Context, msg *QueueMessage) (*SendQueueMessageResult, error) { 282 | return c.transport.SendQueueMessage(ctx, msg) 283 | } 284 | 285 | // SendQueueMessages - send multiple queue messages 286 | func (c *Client) SendQueueMessages(ctx context.Context, msg []*QueueMessage) ([]*SendQueueMessageResult, error) { 287 | return c.transport.SendQueueMessages(ctx, msg) 288 | } 289 | 290 | // NewReceiveQueueMessagesRequest - create an empty receive queue message request object 291 | func (c *Client) NewReceiveQueueMessagesRequest() *ReceiveQueueMessagesRequest { 292 | return c.RQM() 293 | } 294 | 295 | // RQM - create an empty receive queue message request object 296 | func (c *Client) RQM() *ReceiveQueueMessagesRequest { 297 | return &ReceiveQueueMessagesRequest{ 298 | RequestID: "", 299 | ClientID: c.opts.clientId, 300 | Channel: "", 301 | MaxNumberOfMessages: 0, 302 | WaitTimeSeconds: 0, 303 | IsPeak: false, 304 | transport: c.transport, 305 | trace: nil, 306 | } 307 | } 308 | 309 | // ReceiveQueueMessages - call to receive messages from a queue 310 | func (c *Client) ReceiveQueueMessages(ctx context.Context, req *ReceiveQueueMessagesRequest) (*ReceiveQueueMessagesResponse, error) { 311 | return c.transport.ReceiveQueueMessages(ctx, req) 312 | } 313 | 314 | // NewAckAllQueueMessagesRequest - create an empty ack all receive queue messages request object 315 | func (c *Client) NewAckAllQueueMessagesRequest() *AckAllQueueMessagesRequest { 316 | return c.AQM() 317 | } 318 | 319 | // AQM - create an empty ack all receive queue messages request object 320 | func (c *Client) AQM() *AckAllQueueMessagesRequest { 321 | return &AckAllQueueMessagesRequest{ 322 | RequestID: "", 323 | ClientID: c.opts.clientId, 324 | Channel: "", 325 | WaitTimeSeconds: 0, 326 | transport: c.transport, 327 | trace: nil, 328 | } 329 | } 330 | 331 | // AckAllQueueMessages - send ack all messages in queue 332 | func (c *Client) AckAllQueueMessages(ctx context.Context, req *AckAllQueueMessagesRequest) (*AckAllQueueMessagesResponse, error) { 333 | return c.transport.AckAllQueueMessages(ctx, req) 334 | } 335 | 336 | // NewStreamQueueMessage - create an empty stream receive queue message object 337 | func (c *Client) NewStreamQueueMessage() *StreamQueueMessage { 338 | return c.SQM() 339 | } 340 | 341 | // QueuesInfo - get queues detailed information 342 | func (c *Client) QueuesInfo(ctx context.Context, filter string) (*QueuesInfo, error) { 343 | return c.transport.QueuesInfo(ctx, filter) 344 | } 345 | 346 | // SQM - create an empty stream receive queue message object 347 | func (c *Client) SQM() *StreamQueueMessage { 348 | 349 | c.singleStreamQueueMutex <- true 350 | sqm := &StreamQueueMessage{ 351 | RequestID: "", 352 | ClientID: c.opts.clientId, 353 | Channel: "", 354 | visibilitySeconds: 0, 355 | waitTimeSeconds: 0, 356 | refSequence: 0, 357 | reqCh: nil, 358 | resCh: nil, 359 | errCh: nil, 360 | doneCh: nil, 361 | msg: nil, 362 | transport: c.transport, 363 | trace: nil, 364 | ctx: nil, 365 | releaseCh: c.singleStreamQueueMutex, 366 | } 367 | return sqm 368 | } 369 | 370 | // Ping - get status of current connection 371 | func (c *Client) Ping(ctx context.Context) (*ServerInfo, error) { 372 | return c.transport.Ping(ctx) 373 | } 374 | 375 | func (c *Client) SetQueueMessage(qm *QueueMessage) *QueueMessage { 376 | qm.transport = c.transport 377 | qm.trace = nil 378 | return qm 379 | } 380 | func (c *Client) SetEvent(e *Event) *Event { 381 | e.transport = c.transport 382 | return e 383 | } 384 | func (c *Client) SetEventStore(es *EventStore) *EventStore { 385 | es.transport = c.transport 386 | return es 387 | } 388 | 389 | func (c *Client) SetCommand(cmd *Command) *Command { 390 | cmd.transport = c.transport 391 | return cmd 392 | } 393 | 394 | func (c *Client) SetQuery(query *Query) *Query { 395 | query.transport = c.transport 396 | return query 397 | } 398 | 399 | func (c *Client) SetResponse(response *Response) *Response { 400 | response.transport = c.transport 401 | return response 402 | } 403 | -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | type Command struct { 10 | Id string 11 | Channel string 12 | Metadata string 13 | Body []byte 14 | Timeout time.Duration 15 | ClientId string 16 | Tags map[string]string 17 | transport Transport 18 | trace *Trace 19 | } 20 | 21 | func NewCommand() *Command { 22 | return &Command{} 23 | } 24 | 25 | // SetId - set command requestId, otherwise new random uuid will be set 26 | func (c *Command) SetId(id string) *Command { 27 | c.Id = id 28 | return c 29 | } 30 | 31 | // SetClientId - set command ClientId - mandatory if default client was not set 32 | func (c *Command) SetClientId(clientId string) *Command { 33 | c.ClientId = clientId 34 | return c 35 | } 36 | 37 | // SetChannel - set command channel - mandatory if default channel was not set 38 | func (c *Command) SetChannel(channel string) *Command { 39 | c.Channel = channel 40 | return c 41 | } 42 | 43 | // SetMetadata - set command metadata - mandatory if body field is empty 44 | func (c *Command) SetMetadata(metadata string) *Command { 45 | c.Metadata = metadata 46 | return c 47 | } 48 | 49 | // SetBody - set command body - mandatory if metadata field is empty 50 | func (c *Command) SetBody(body []byte) *Command { 51 | c.Body = body 52 | return c 53 | } 54 | 55 | // SetTimeout - set timeout for command to be returned. if timeout expired , send command will result with an error 56 | func (c *Command) SetTimeout(timeout time.Duration) *Command { 57 | c.Timeout = timeout 58 | return c 59 | } 60 | 61 | // SetTags - set key value tags to command message 62 | func (c *Command) SetTags(tags map[string]string) *Command { 63 | c.Tags = map[string]string{} 64 | for key, value := range tags { 65 | c.Tags[key] = value 66 | } 67 | return c 68 | } 69 | 70 | // AddTag - add key value tags to command message 71 | func (c *Command) AddTag(key, value string) *Command { 72 | if c.Tags == nil { 73 | c.Tags = map[string]string{} 74 | } 75 | c.Tags[key] = value 76 | return c 77 | } 78 | 79 | // AddTrace - add tracing support to command 80 | func (c *Command) AddTrace(name string) *Trace { 81 | c.trace = CreateTrace(name) 82 | return c.trace 83 | } 84 | 85 | // Send - sending command , waiting for response or timeout 86 | func (c *Command) Send(ctx context.Context) (*CommandResponse, error) { 87 | if c.transport == nil { 88 | return nil, ErrNoTransportDefined 89 | } 90 | return c.transport.SendCommand(ctx, c) 91 | } 92 | 93 | type CommandReceive struct { 94 | Id string 95 | ClientId string 96 | Channel string 97 | Metadata string 98 | Body []byte 99 | ResponseTo string 100 | Tags map[string]string 101 | } 102 | 103 | func (cr *CommandReceive) String() string { 104 | return fmt.Sprintf("Id: %s, ClientId: %s, Channel: %s, Metadata: %s, Body: %s, ResponseTo: %s, Tags: %v", cr.Id, cr.ClientId, cr.Channel, cr.Metadata, string(cr.Body), cr.ResponseTo, cr.Tags) 105 | } 106 | 107 | type CommandResponse struct { 108 | CommandId string 109 | ResponseClientId string 110 | Executed bool 111 | ExecutedAt time.Time 112 | Error string 113 | Tags map[string]string 114 | } 115 | 116 | func (cr *CommandResponse) String() string { 117 | return fmt.Sprintf("CommandId: %s, ResponseClientId: %s, Executed: %v, ExecutedAt: %s, Error: %s, Tags: %v", cr.CommandId, cr.ResponseClientId, cr.Executed, cr.ExecutedAt.String(), cr.Error, cr.Tags) 118 | } 119 | -------------------------------------------------------------------------------- /commands_client.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/kubemq-io/kubemq-go/common" 7 | ) 8 | 9 | // CommandsClient represents a client that can be used to send commands to a server. 10 | // It contains a reference to the underlying client that handles the communication. 11 | type CommandsClient struct { 12 | client *Client 13 | } 14 | 15 | // CommandsSubscription represents a subscription to commands requests by channel and group. 16 | // It contains the following fields: 17 | // - Channel: the channel to subscribe to 18 | // - Group: the group to subscribe to 19 | // - ClientId: the ID of the client subscribing to the commands 20 | // 21 | // Usage example: 22 | // 23 | // commandsSubscription := &CommandsSubscription{ 24 | // Channel: "channel", 25 | // Group: "group", 26 | // ClientId: "clientID", 27 | // } 28 | // err := commandsSubscription.Validate() 29 | // if err != nil { 30 | // // handle validation error 31 | // } 32 | // commandsCh, err := client.SubscribeToCommands(context.Background(), commandsSubscription, errCh) 33 | // if err != nil { 34 | // // handle subscribe error 35 | // } 36 | // for command := range commandsCh { 37 | // // handle received command 38 | // } 39 | // 40 | // It also has the following methods: 41 | // 42 | // Complete(opts *Options) *CommandsSubscription: completes the commands subscription with the given options 43 | // Validate() error: validates the commands subscription, ensuring that it has a channel and client ID 44 | type CommandsSubscription struct { 45 | Channel string 46 | Group string 47 | ClientId string 48 | } 49 | 50 | // Complete method sets the `ClientId` field of the `CommandsSubscription` struct if it is empty. 51 | // It takes an `Options` object as a parameter, and uses the `clientId` field of the `Options` object 52 | // to set the `ClientId` field of `CommandsSubscription` if it is empty. It returns a pointer to the 53 | // modified `CommandsSubscription` object. 54 | // 55 | // Example usage: 56 | // 57 | // request := &CommandsSubscription{ 58 | // Channel: "my-channel", 59 | // Group: "my-group", 60 | // } 61 | // options := &Options{ 62 | // clientId: "my-client-id", 63 | // } 64 | // request.Complete(options) 65 | // // Now the `ClientId` field of `request` will be set as "my-client-id" if it was empty. 66 | func (cs *CommandsSubscription) Complete(opts *Options) *CommandsSubscription { 67 | if cs.ClientId == "" { 68 | cs.ClientId = opts.clientId 69 | } 70 | return cs 71 | } 72 | 73 | // Validate checks if a CommandsSubscription object has valid channel and clientId values. 74 | // It returns an error if any of the required fields is empty. Otherwise, it returns nil. 75 | func (cs *CommandsSubscription) Validate() error { 76 | if cs.Channel == "" { 77 | return fmt.Errorf("commands subscription must have a channel") 78 | } 79 | if cs.ClientId == "" { 80 | return fmt.Errorf("commands subscription must have a clientId") 81 | } 82 | return nil 83 | } 84 | 85 | // NewCommandsClient creates a new instance of CommandsClient with the provided context and options. 86 | // It returns the created CommandsClient instance and an error if any. 87 | func NewCommandsClient(ctx context.Context, op ...Option) (*CommandsClient, error) { 88 | client, err := NewClient(ctx, op...) 89 | if err != nil { 90 | return nil, err 91 | } 92 | return &CommandsClient{ 93 | client: client, 94 | }, nil 95 | } 96 | 97 | // Send sends a command using the provided context and command request. It checks if the client is ready to send the command. It sets the transport for the request and calls the Send method on the client to send the command. It returns the command response and any error that occurred during the process. 98 | func (c *CommandsClient) Send(ctx context.Context, request *Command) (*CommandResponse, error) { 99 | if err := c.isClientReady(); err != nil { 100 | return nil, err 101 | } 102 | request.transport = c.client.transport 103 | return c.client.SetCommand(request).Send(ctx) 104 | } 105 | 106 | // Response sets the response object in the CommandsClient 107 | // and sends the response using the client's transport. 108 | // 109 | // This method requires the client to be initialized. 110 | // 111 | // Parameters: 112 | // 113 | // ctx: The context.Context object for the request. 114 | // response: The Response object to set and send. 115 | // 116 | // Returns: 117 | // - error: An error if the client is not ready or if sending the response fails. 118 | func (c *CommandsClient) Response(ctx context.Context, response *Response) error { 119 | if err := c.isClientReady(); err != nil { 120 | return err 121 | } 122 | response.transport = c.client.transport 123 | return c.client.SetResponse(response).Send(ctx) 124 | } 125 | 126 | // Subscribe starts a subscription to receive commands from the server. 127 | // It takes a context and a CommandsSubscription request as input, 128 | // along with a callback function onCommandReceive that will be invoked 129 | // whenever a command is received or an error occurs. 130 | // It returns an error if the client is not ready, 131 | // if the callback function is nil, or if the request fails validation. 132 | // The Subscribe method launches a goroutine that listens for incoming 133 | // commands and triggers the callback function accordingly. 134 | func (c *CommandsClient) Subscribe(ctx context.Context, request *CommandsSubscription, onCommandReceive func(cmd *CommandReceive, err error)) error { 135 | if err := c.isClientReady(); err != nil { 136 | return err 137 | } 138 | if onCommandReceive == nil { 139 | return fmt.Errorf("commands request subscription callback function is required") 140 | } 141 | if err := request.Complete(c.client.opts).Validate(); err != nil { 142 | return err 143 | } 144 | errCh := make(chan error, 1) 145 | commandsCh, err := c.client.SubscribeToCommandsWithRequest(ctx, request, errCh) 146 | if err != nil { 147 | return err 148 | } 149 | go func() { 150 | for { 151 | select { 152 | case cmd := <-commandsCh: 153 | onCommandReceive(cmd, nil) 154 | case err := <-errCh: 155 | onCommandReceive(nil, err) 156 | case <-ctx.Done(): 157 | return 158 | } 159 | } 160 | }() 161 | return nil 162 | } 163 | 164 | // Create sends a request to create a channel of type "commands" with the given channel name. 165 | // 166 | // It returns an error if there was a failure in sending the create channel request, or if there was an error creating the channel. 167 | func (c *CommandsClient) Create(ctx context.Context, channel string) error { 168 | return CreateChannel(ctx, c.client, c.client.opts.clientId, channel, "commands") 169 | } 170 | 171 | // Delete deletes a channel from the commands client. 172 | // 173 | // It sends a delete channel request to the KubeMQ server and returns an error if there is any. 174 | // The function constructs a delete channel query, sets the required metadata and timeout, and makes the request through the client's query service. 175 | // If the response contains an error message, it returns an error. 176 | // 177 | // ctx: The context.Context object for the request. 178 | // channel: The name of the channel to be deleted. 179 | // 180 | // Returns: 181 | // - nil if the channel was deleted successfully. 182 | // - An error if the channel deletion failed. 183 | func (c *CommandsClient) Delete(ctx context.Context, channel string) error { 184 | return DeleteChannel(ctx, c.client, c.client.opts.clientId, channel, "commands") 185 | } 186 | 187 | // List returns a list of CQChannels that match the given search criteria. 188 | // It uses the ListCQChannels function to retrieve the data and decode it into CQChannel objects. 189 | // The search parameter is optional and can be used to filter the results. 190 | // The function requires a context, a client, a client ID, a channel type, and a search string. 191 | // It returns a slice of CQChannel objects and an error if any occurred. 192 | func (c *CommandsClient) List(ctx context.Context, search string) ([]*common.CQChannel, error) { 193 | return ListCQChannels(ctx, c.client, c.client.opts.clientId, "commands", search) 194 | } 195 | 196 | // Close closes the connection to the server by invoking the Close method of the underlying client. 197 | // It returns an error if the close operation fails. 198 | func (c *CommandsClient) Close() error { 199 | return c.client.Close() 200 | } 201 | 202 | // isClientReady checks if the client is ready for use. If the client is not 203 | // initialized, it returns an error. If the client is initialized, it returns nil. 204 | func (c *CommandsClient) isClientReady() error { 205 | if c.client == nil { 206 | return fmt.Errorf("client is not initialized") 207 | } 208 | return nil 209 | } 210 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/kubemq-io/kubemq-go/common" 8 | "github.com/kubemq-io/kubemq-go/pkg/uuid" 9 | "time" 10 | ) 11 | 12 | const requestChannel = "kubemq.cluster.internal.requests" 13 | 14 | func CreateChannel(ctx context.Context, client *Client, clientId string, channel string, channelType string) error { 15 | request := NewQuery(). 16 | SetChannel(requestChannel). 17 | SetId(uuid.New()). 18 | SetClientId(clientId). 19 | SetMetadata("create-channel"). 20 | SetTags(map[string]string{ 21 | "channel_type": channelType, 22 | "channel": channel, 23 | "client_id": clientId, 24 | }). 25 | SetTimeout(time.Second * 10) 26 | resp, err := client.SetQuery(request).Send(ctx) 27 | if err != nil { 28 | return fmt.Errorf("error sending create channel request: %s", err.Error()) 29 | } 30 | if resp.Error != "" { 31 | return fmt.Errorf("error creating channel: %s", resp.Error) 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func DeleteChannel(ctx context.Context, client *Client, clientId string, channel string, channelType string) error { 38 | request := NewQuery(). 39 | SetChannel(requestChannel). 40 | SetId(uuid.New()). 41 | SetClientId(clientId). 42 | SetMetadata("delete-channel"). 43 | SetTags(map[string]string{ 44 | "channel_type": channelType, 45 | "channel": channel, 46 | "client_id": clientId, 47 | }). 48 | SetTimeout(time.Second * 10) 49 | resp, err := client.SetQuery(request).Send(ctx) 50 | if err != nil { 51 | return fmt.Errorf("error sending delete channel request: %s", err.Error()) 52 | } 53 | if resp.Error != "" { 54 | return fmt.Errorf("error deleting channel: %s", resp.Error) 55 | } 56 | return nil 57 | } 58 | 59 | func listChannels(ctx context.Context, client *Client, clientId string, channelType string, search string) ([]byte, error) { 60 | request := NewQuery(). 61 | SetChannel(requestChannel). 62 | SetId(uuid.New()). 63 | SetClientId(clientId). 64 | SetMetadata("list-channels"). 65 | SetTags(map[string]string{ 66 | "channel_type": channelType, 67 | "client_id": clientId, 68 | "search": search, 69 | }). 70 | SetTimeout(time.Second * 10) 71 | resp, err := client.SetQuery(request).Send(ctx) 72 | if err != nil { 73 | return nil, fmt.Errorf("error sending list channels request: %s", err.Error()) 74 | } 75 | if resp.Error != "" { 76 | return nil, fmt.Errorf("error listing channels: %s", resp.Error) 77 | } 78 | return resp.Body, nil 79 | } 80 | func ListQueuesChannels(ctx context.Context, client *Client, clientId string, search string) ([]*common.QueuesChannel, error) { 81 | data, err := listChannels(ctx, client, clientId, "queues", search) 82 | if err != nil { 83 | return nil, err 84 | 85 | } 86 | return DecodeQueuesChannelList(data) 87 | } 88 | 89 | func ListPubSubChannels(ctx context.Context, client *Client, clientId string, channelType string, search string) ([]*common.PubSubChannel, error) { 90 | data, err := listChannels(ctx, client, clientId, channelType, search) 91 | if err != nil { 92 | return nil, err 93 | 94 | } 95 | return DecodePubSubChannelList(data) 96 | } 97 | 98 | func ListCQChannels(ctx context.Context, client *Client, clientId string, channelType string, search string) ([]*common.CQChannel, error) { 99 | data, err := listChannels(ctx, client, clientId, channelType, search) 100 | if err != nil { 101 | return nil, err 102 | } 103 | return DecodeCQChannelList(data) 104 | } 105 | 106 | func DecodePubSubChannelList(dataBytes []byte) ([]*common.PubSubChannel, error) { 107 | var channelsData []*common.PubSubChannel 108 | if dataBytes == nil { 109 | return nil, nil 110 | } 111 | err := json.Unmarshal(dataBytes, &channelsData) 112 | if err != nil { 113 | return nil, err 114 | } 115 | return channelsData, nil 116 | } 117 | 118 | func DecodeQueuesChannelList(dataBytes []byte) ([]*common.QueuesChannel, error) { 119 | var channelsData []*common.QueuesChannel 120 | if dataBytes == nil { 121 | return nil, nil 122 | } 123 | err := json.Unmarshal(dataBytes, &channelsData) 124 | if err != nil { 125 | return nil, err 126 | } 127 | return channelsData, nil 128 | } 129 | 130 | func DecodeCQChannelList(dataBytes []byte) ([]*common.CQChannel, error) { 131 | var channelsData []*common.CQChannel 132 | if dataBytes == nil { 133 | return nil, nil 134 | } 135 | err := json.Unmarshal(dataBytes, &channelsData) 136 | if err != nil { 137 | return nil, err 138 | } 139 | return channelsData, nil 140 | } 141 | -------------------------------------------------------------------------------- /common/channel_stats.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type QueuesStats struct { 8 | Messages int `json:"messages"` 9 | Volume int `json:"volume"` 10 | Waiting int `json:"waiting"` 11 | Expired int `json:"expired"` 12 | Delayed int `json:"delayed"` 13 | } 14 | 15 | func (qs *QueuesStats) String() string { 16 | return fmt.Sprintf("Stats: messages=%d, volume=%d, waiting=%d, expired=%d, delayed=%d", qs.Messages, qs.Volume, qs.Waiting, qs.Expired, qs.Delayed) 17 | } 18 | 19 | type QueuesChannel struct { 20 | Name string `json:"name"` 21 | Type string `json:"type"` 22 | LastActivity int `json:"lastActivity"` 23 | IsActive bool `json:"isActive"` 24 | Incoming QueuesStats `json:"incoming"` 25 | Outgoing QueuesStats `json:"outgoing"` 26 | } 27 | 28 | func (qc *QueuesChannel) String() string { 29 | return fmt.Sprintf("Channel: name=%s, type=%s, last_activity=%d, is_active=%t, incoming=%v, outgoing=%v", qc.Name, qc.Type, qc.LastActivity, qc.IsActive, qc.Incoming, qc.Outgoing) 30 | } 31 | 32 | type PubSubStats struct { 33 | Messages int `json:"messages"` 34 | Volume int `json:"volume"` 35 | } 36 | 37 | func (ps *PubSubStats) String() string { 38 | return fmt.Sprintf("Stats: messages=%d, volume=%d", ps.Messages, ps.Volume) 39 | } 40 | 41 | type PubSubChannel struct { 42 | Name string `json:"name"` 43 | Type string `json:"type"` 44 | LastActivity int `json:"lastActivity"` 45 | IsActive bool `json:"isActive"` 46 | Incoming PubSubStats `json:"incoming"` 47 | Outgoing PubSubStats `json:"outgoing"` 48 | } 49 | 50 | func (pc *PubSubChannel) String() string { 51 | return fmt.Sprintf("Channel: name=%s, type=%s, last_activity=%d, is_active=%t, incoming=%v, outgoing=%v", pc.Name, pc.Type, pc.LastActivity, pc.IsActive, pc.Incoming, pc.Outgoing) 52 | } 53 | 54 | type CQStats struct { 55 | Messages int `json:"messages"` 56 | Volume int `json:"volume"` 57 | Responses int `json:"responses"` 58 | } 59 | 60 | func (cs *CQStats) String() string { 61 | return fmt.Sprintf("Stats: messages=%d, volume=%d, responses=%d", cs.Messages, cs.Volume, cs.Responses) 62 | } 63 | 64 | type CQChannel struct { 65 | Name string `json:"name"` 66 | Type string `json:"type"` 67 | LastActivity int `json:"lastActivity"` 68 | IsActive bool `json:"isActive"` 69 | Incoming CQStats `json:"incoming"` 70 | Outgoing CQStats `json:"outgoing"` 71 | } 72 | 73 | func (cc *CQChannel) String() string { 74 | return fmt.Sprintf("Channel: name=%s, type=%s, last_activity=%d, is_active=%t, incoming=%v, outgoing=%v", cc.Name, cc.Type, cc.LastActivity, cc.IsActive, cc.Incoming, cc.Outgoing) 75 | } 76 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | type Event struct { 9 | Id string 10 | Channel string 11 | Metadata string 12 | Body []byte 13 | ClientId string 14 | Tags map[string]string 15 | transport Transport 16 | } 17 | 18 | func NewEvent() *Event { 19 | return &Event{} 20 | } 21 | 22 | // SetId - set event id otherwise new random uuid will be set 23 | func (e *Event) SetId(id string) *Event { 24 | e.Id = id 25 | return e 26 | } 27 | 28 | // SetClientId - set event ClientId - mandatory if default client was not set 29 | func (e *Event) SetClientId(clientId string) *Event { 30 | e.ClientId = clientId 31 | return e 32 | } 33 | 34 | // SetMetadata - set event metadata - mandatory if body field was not set 35 | func (e *Event) SetMetadata(metadata string) *Event { 36 | e.Metadata = metadata 37 | return e 38 | } 39 | 40 | // SetChannel - set event channel - mandatory if default channel was not set 41 | func (e *Event) SetChannel(channel string) *Event { 42 | e.Channel = channel 43 | return e 44 | } 45 | 46 | // SetBody - set event body - mandatory if metadata field was not set 47 | func (e *Event) SetBody(body []byte) *Event { 48 | e.Body = body 49 | return e 50 | } 51 | 52 | // SetTags - set key value tags to event message 53 | func (e *Event) SetTags(tags map[string]string) *Event { 54 | e.Tags = map[string]string{} 55 | for key, value := range tags { 56 | e.Tags[key] = value 57 | } 58 | return e 59 | } 60 | 61 | // AddTag - add key value tags to event message 62 | func (e *Event) AddTag(key, value string) *Event { 63 | if e.Tags == nil { 64 | e.Tags = map[string]string{} 65 | } 66 | e.Tags[key] = value 67 | return e 68 | } 69 | 70 | func (e *Event) Send(ctx context.Context) error { 71 | if e.transport == nil { 72 | return ErrNoTransportDefined 73 | } 74 | return e.transport.SendEvent(ctx, e) 75 | } 76 | 77 | func (e *Event) String() string { 78 | return fmt.Sprintf("Id: %s, Channel: %s, Metadata: %s, Body: %s, ClientId: %s, Tags: %s", e.Id, e.Channel, e.Metadata, e.Body, e.ClientId, e.Tags) 79 | } 80 | -------------------------------------------------------------------------------- /event_store.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | pb "github.com/kubemq-io/protobuf/go" 9 | ) 10 | 11 | type EventStore struct { 12 | Id string 13 | Channel string 14 | Metadata string 15 | Body []byte 16 | ClientId string 17 | Tags map[string]string 18 | transport Transport 19 | } 20 | 21 | func NewEventStore() *EventStore { 22 | return &EventStore{} 23 | } 24 | 25 | // SetId - set event store id otherwise new random uuid will be set 26 | func (es *EventStore) SetId(id string) *EventStore { 27 | es.Id = id 28 | return es 29 | } 30 | 31 | // SetClientId - set event store ClientId - mandatory if default client was not set 32 | func (es *EventStore) SetClientId(clientId string) *EventStore { 33 | es.ClientId = clientId 34 | return es 35 | } 36 | 37 | // SetMetadata - set event store metadata - mandatory if body field was not set 38 | func (es *EventStore) SetMetadata(metadata string) *EventStore { 39 | es.Metadata = metadata 40 | return es 41 | } 42 | 43 | // SetChannel - set event store channel - mandatory if default channel was not set 44 | func (es *EventStore) SetChannel(channel string) *EventStore { 45 | es.Channel = channel 46 | return es 47 | } 48 | 49 | // SetBody - set event store body - mandatory if metadata field was not set 50 | func (es *EventStore) SetBody(body []byte) *EventStore { 51 | es.Body = body 52 | return es 53 | } 54 | 55 | // SetTags - set key value tags to event store message 56 | func (es *EventStore) SetTags(tags map[string]string) *EventStore { 57 | es.Tags = map[string]string{} 58 | for key, value := range tags { 59 | es.Tags[key] = value 60 | } 61 | return es 62 | } 63 | 64 | // AddTag - add key value tags to event store message 65 | func (es *EventStore) AddTag(key, value string) *EventStore { 66 | if es.Tags == nil { 67 | es.Tags = map[string]string{} 68 | } 69 | es.Tags[key] = value 70 | return es 71 | } 72 | 73 | // Send - sending events store message 74 | func (es *EventStore) Send(ctx context.Context) (*EventStoreResult, error) { 75 | if es.transport == nil { 76 | return nil, ErrNoTransportDefined 77 | } 78 | return es.transport.SendEventStore(ctx, es) 79 | } 80 | 81 | type EventStoreResult struct { 82 | Id string 83 | Sent bool 84 | Err error 85 | } 86 | 87 | type EventStoreReceive struct { 88 | Id string 89 | Sequence uint64 90 | Timestamp time.Time 91 | Channel string 92 | Metadata string 93 | Body []byte 94 | ClientId string 95 | Tags map[string]string 96 | } 97 | 98 | func (es *EventStoreReceive) String() string { 99 | return fmt.Sprintf("Id: %s, Sequence: %d, Timestamp: %s, Channel: %s, Metadata: %s, Body: %s, ClientId: %s, Tags: %s", es.Id, es.Sequence, es.Timestamp.String(), es.Channel, es.Metadata, es.Body, es.ClientId, es.Tags) 100 | 101 | } 102 | 103 | type SubscriptionOption interface { 104 | apply(*subscriptionOption) 105 | } 106 | 107 | type subscriptionOption struct { 108 | kind pb.Subscribe_EventsStoreType 109 | value int64 110 | } 111 | 112 | type funcSubscriptionOptions struct { 113 | fn func(*subscriptionOption) 114 | } 115 | 116 | func (fo *funcSubscriptionOptions) apply(o *subscriptionOption) { 117 | fo.fn(o) 118 | } 119 | 120 | func newFuncSubscriptionOption(f func(*subscriptionOption)) *funcSubscriptionOptions { 121 | return &funcSubscriptionOptions{ 122 | fn: f, 123 | } 124 | } 125 | 126 | // StartFromNewEvents - start event store subscription with only new events 127 | func StartFromNewEvents() SubscriptionOption { 128 | return newFuncSubscriptionOption(func(o *subscriptionOption) { 129 | o.kind = pb.Subscribe_StartNewOnly 130 | }) 131 | } 132 | 133 | // StartFromFirstEvent - replay all the stored events from the first available sequence and continue stream new events from this point 134 | func StartFromFirstEvent() SubscriptionOption { 135 | return newFuncSubscriptionOption(func(o *subscriptionOption) { 136 | o.kind = pb.Subscribe_StartFromFirst 137 | }) 138 | } 139 | 140 | // StartFromLastEvent - replay last event and continue stream new events from this point 141 | func StartFromLastEvent() SubscriptionOption { 142 | return newFuncSubscriptionOption(func(o *subscriptionOption) { 143 | o.kind = pb.Subscribe_StartFromLast 144 | }) 145 | } 146 | 147 | // StartFromSequence - replay events from specific event sequence number and continue stream new events from this point 148 | func StartFromSequence(sequence int) SubscriptionOption { 149 | return newFuncSubscriptionOption(func(o *subscriptionOption) { 150 | o.kind = pb.Subscribe_StartAtSequence 151 | o.value = int64(sequence) 152 | }) 153 | } 154 | 155 | // StartFromTime - replay events from specific time continue stream new events from this point 156 | func StartFromTime(since time.Time) SubscriptionOption { 157 | return newFuncSubscriptionOption(func(o *subscriptionOption) { 158 | o.kind = pb.Subscribe_StartAtTime 159 | o.value = since.UnixNano() 160 | }) 161 | } 162 | 163 | // StartFromTimeDelta - replay events from specific current time - delta duration in seconds, continue stream new events from this point 164 | func StartFromTimeDelta(delta time.Duration) SubscriptionOption { 165 | return newFuncSubscriptionOption(func(o *subscriptionOption) { 166 | o.kind = pb.Subscribe_StartAtTimeDelta 167 | o.value = int64(delta.Seconds()) 168 | }) 169 | } 170 | -------------------------------------------------------------------------------- /events_client.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/kubemq-io/kubemq-go/common" 7 | ) 8 | 9 | // EventsMessageHandler is a function type that takes in a pointer to an Event object and does not return anything. 10 | type EventsMessageHandler func(*Event) 11 | 12 | // EventsErrorsHandler is a type representing a function that handles errors for events. 13 | type EventsErrorsHandler func(error) 14 | 15 | // EventsClient is a client for interacting with events. 16 | // It encapsulates a client for making API requests. 17 | type EventsClient struct { 18 | client *Client 19 | } 20 | 21 | // EventsSubscription represents a subscription to events by channel and group. 22 | type EventsSubscription struct { 23 | Channel string 24 | Group string 25 | ClientId string 26 | } 27 | 28 | // Complete sets the ClientId of the EventsSubscription if it is empty. 29 | // It takes an *Options argument to retrieve the clientId value. If the ClientId 30 | // is already set in the EventsSubscription, it will not be overridden. 31 | // It returns a pointer to the modified EventsSubscription. 32 | func (es *EventsSubscription) Complete(opts *Options) *EventsSubscription { 33 | if es.ClientId == "" { 34 | es.ClientId = opts.clientId 35 | } 36 | return es 37 | } 38 | 39 | // Validate checks if the EventsSubscription has a non-empty Channel and ClientId. 40 | // If either of them is empty, it returns an error. 41 | // Otherwise, it returns nil. 42 | func (es *EventsSubscription) Validate() error { 43 | if es.Channel == "" { 44 | return fmt.Errorf("events subscription must have a channel") 45 | } 46 | if es.ClientId == "" { 47 | return fmt.Errorf("events subscription must have a clientId") 48 | } 49 | return nil 50 | } 51 | 52 | // NewEventsClient creates an instance of EventsClient by calling NewClient and returning EventsClient{client} 53 | // 54 | // Parameters: 55 | // - ctx: The context.Context to be used in NewClient call. 56 | // - op: Optional parameters of type Option to be passed to NewClient. 57 | // 58 | // Returns a pointer to EventsClient and an error if NewClient call fails. 59 | func NewEventsClient(ctx context.Context, op ...Option) (*EventsClient, error) { 60 | client, err := NewClient(ctx, op...) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return &EventsClient{ 65 | client: client, 66 | }, nil 67 | } 68 | 69 | // Check if the client is ready 70 | func (e *EventsClient) Send(ctx context.Context, message *Event) error { 71 | if err := e.isClientReady(); err != nil { 72 | return err 73 | } 74 | message.transport = e.client.transport 75 | return e.client.SetEvent(message).Send(ctx) 76 | } 77 | 78 | // Stream sends events from client to server and receives events from server to client. 79 | // It takes a context as input, which can be used to cancel the streaming process. 80 | // It also takes an onError function callback, which will be called when an error occurs during the streaming process. 81 | // The method returns a sendFunc function, which can be used to send events to the server, 82 | // and an error, which will be non-nil if the client is not ready or if the onError callback is not provided. 83 | // The sendFunc function takes an event message as input and returns an error. 84 | // It sends the event to the server through a channel, and if the context is cancelled before the event is sent, 85 | // it returns an error indicating that the context was cancelled during event message sending. 86 | // The method starts two goroutines, one for sending events to the server and one for receiving events from the server. 87 | // The sending goroutine sends events to the server by accepting events from the eventsCh channel. 88 | // The receiving goroutine receives errors from the errCh channel and calls the onError callback for each error received. 89 | // It also checks if the context is cancelled and stops the receiving goroutine if it is. 90 | // The method returns the sendFunc function and a nil error. 91 | func (e *EventsClient) Stream(ctx context.Context, onError func(err error)) (func(msg *Event) error, error) { 92 | if err := e.isClientReady(); err != nil { 93 | return nil, err 94 | } 95 | if onError == nil { 96 | return nil, fmt.Errorf("events stream error callback function is required") 97 | } 98 | errCh := make(chan error, 1) 99 | eventsCh := make(chan *Event, 1) 100 | sendFunc := func(msg *Event) error { 101 | select { 102 | case eventsCh <- msg: 103 | return nil 104 | 105 | case <-ctx.Done(): 106 | return fmt.Errorf("context canceled during events message sending") 107 | } 108 | } 109 | go e.client.StreamEvents(ctx, eventsCh, errCh) 110 | go func() { 111 | for { 112 | select { 113 | case err := <-errCh: 114 | onError(err) 115 | case <-ctx.Done(): 116 | return 117 | } 118 | } 119 | }() 120 | return sendFunc, nil 121 | } 122 | 123 | // Subscribe subscribes to events using the provided EventsSubscription and callback function. 124 | // It checks if the client is ready and if the callback function is provided. 125 | // It validates the subscription request. 126 | // It creates a channel for errors, subscribes to events with the request and initializes an events channel. 127 | // It starts a goroutine to listen for events or errors and calls the callback function accordingly. 128 | // If the context is canceled, it returns. 129 | // It returns an error if any. 130 | func (e *EventsClient) Subscribe(ctx context.Context, request *EventsSubscription, onEvent func(msg *Event, err error)) error { 131 | if err := e.isClientReady(); err != nil { 132 | return err 133 | } 134 | if onEvent == nil { 135 | return fmt.Errorf("events subscription callback function is required") 136 | } 137 | if err := request.Complete(e.client.opts).Validate(); err != nil { 138 | return err 139 | } 140 | errCh := make(chan error, 1) 141 | eventsCh, err := e.client.SubscribeToEventsWithRequest(ctx, request, errCh) 142 | if err != nil { 143 | return err 144 | } 145 | go func() { 146 | for { 147 | select { 148 | case msg := <-eventsCh: 149 | onEvent(msg, nil) 150 | case err := <-errCh: 151 | onEvent(nil, err) 152 | case <-ctx.Done(): 153 | return 154 | } 155 | } 156 | }() 157 | return nil 158 | } 159 | 160 | // Create creates a new event channel with the specified channel name. 161 | // It sends a create-channel request to the KubeMQ server using the provided context and client. 162 | // The channelType parameter specifies the type of the channel ('events' in this case). 163 | // It returns an error if an error occurs during the creation of the channel. 164 | func (e *EventsClient) Create(ctx context.Context, channel string) error { 165 | return CreateChannel(ctx, e.client, e.client.opts.clientId, channel, "events") 166 | } 167 | 168 | // Delete deletes a channel from the events client. 169 | // It sends a delete channel request with the specified channel ID and type to the client. 170 | // Returns an error if the delete channel request fails or if there is an error deleting the channel. 171 | // 172 | // Example usage: 173 | // 174 | // err := eventsClient.Delete(ctx, "events.A") 175 | // if err != nil { 176 | // log.Fatal(err) 177 | // } 178 | func (e *EventsClient) Delete(ctx context.Context, channel string) error { 179 | return DeleteChannel(ctx, e.client, e.client.opts.clientId, channel, "events") 180 | } 181 | 182 | // List retrieves a list of PubSubChannels based on the provided search string. 183 | // It calls ListPubSubChannels function with the given context, EventsClient's client, 184 | // client ID, channel type "events", and the search string. 185 | // It returns a slice of PubSubChannel pointers and an error. 186 | func (e *EventsClient) List(ctx context.Context, search string) ([]*common.PubSubChannel, error) { 187 | return ListPubSubChannels(ctx, e.client, e.client.opts.clientId, "events", search) 188 | } 189 | 190 | // Close closes the EventsClient by invoking the Close method on its underlying Client. 191 | // It returns an error if there was a problem closing the client. 192 | func (e *EventsClient) Close() error { 193 | return e.client.Close() 194 | } 195 | 196 | // isClientReady checks if the client is initialized. If the client is not initialized, 197 | // it returns an error indicating that the client is not ready. Otherwise, it returns nil. 198 | func (e *EventsClient) isClientReady() error { 199 | if e.client == nil { 200 | return fmt.Errorf("client is not initialized") 201 | } 202 | return nil 203 | } 204 | -------------------------------------------------------------------------------- /events_store_client.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/kubemq-io/kubemq-go/common" 7 | ) 8 | 9 | // EventsStoreClient is a struct that holds a client instance. 10 | type EventsStoreClient struct { 11 | client *Client 12 | } 13 | 14 | // EventsStoreSubscription is a struct that holds the subscription details. 15 | type EventsStoreSubscription struct { 16 | Channel string 17 | Group string 18 | ClientId string 19 | SubscriptionType SubscriptionOption 20 | } 21 | 22 | // Complete is a method that completes the subscription with the provided options. 23 | func (es *EventsStoreSubscription) Complete(opts *Options) *EventsStoreSubscription { 24 | if es.ClientId == "" { 25 | es.ClientId = opts.clientId 26 | } 27 | return es 28 | } 29 | 30 | // Validate is a method that validates the subscription details. 31 | func (es *EventsStoreSubscription) Validate() error { 32 | if es.Channel == "" { 33 | return fmt.Errorf("events store subscription must have a channel") 34 | } 35 | if es.ClientId == "" { 36 | return fmt.Errorf("events store subscription must have a clientId") 37 | } 38 | if es.SubscriptionType == nil { 39 | return fmt.Errorf("events store subscription must have a subscription type") 40 | } 41 | return nil 42 | } 43 | 44 | // NewEventsStoreClient is a function that creates a new EventsStoreClient. 45 | func NewEventsStoreClient(ctx context.Context, op ...Option) (*EventsStoreClient, error) { 46 | client, err := NewClient(ctx, op...) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return &EventsStoreClient{ 51 | client: client, 52 | }, nil 53 | } 54 | 55 | // Send is a method that sends an event to the store. 56 | func (es *EventsStoreClient) Send(ctx context.Context, message *EventStore) (*EventStoreResult, error) { 57 | if err := es.isClientReady(); err != nil { 58 | return nil, err 59 | } 60 | message.transport = es.client.transport 61 | return es.client.SetEventStore(message).Send(ctx) 62 | } 63 | 64 | // Stream is a method that streams events from the store. 65 | func (es *EventsStoreClient) Stream(ctx context.Context, onResult func(result *EventStoreResult, err error)) (func(msg *EventStore) error, error) { 66 | if err := es.isClientReady(); err != nil { 67 | return nil, err 68 | } 69 | if onResult == nil { 70 | return nil, fmt.Errorf("events stream result callback function is required") 71 | } 72 | errCh := make(chan error, 1) 73 | eventsCh := make(chan *EventStore, 1) 74 | 75 | sendFunc := func(msg *EventStore) error { 76 | select { 77 | case eventsCh <- msg: 78 | return nil 79 | 80 | case <-ctx.Done(): 81 | return fmt.Errorf("context canceled during events message sending") 82 | } 83 | } 84 | eventsResultCh := make(chan *EventStoreResult, 1) 85 | go es.client.StreamEventsStore(ctx, eventsCh, eventsResultCh, errCh) 86 | go func() { 87 | for { 88 | select { 89 | case result, ok := <-eventsResultCh: 90 | if !ok { 91 | return 92 | } 93 | onResult(result, nil) 94 | case err := <-errCh: 95 | onResult(nil, err) 96 | case <-ctx.Done(): 97 | return 98 | } 99 | } 100 | }() 101 | return sendFunc, nil 102 | } 103 | 104 | // Subscribe is a method that subscribes to events from the store. 105 | func (es *EventsStoreClient) Subscribe(ctx context.Context, request *EventsStoreSubscription, onEvent func(msg *EventStoreReceive, err error)) error { 106 | if err := es.isClientReady(); err != nil { 107 | return err 108 | } 109 | if onEvent == nil { 110 | return fmt.Errorf("events store subscription callback function is required") 111 | } 112 | if err := request.Complete(es.client.opts).Validate(); err != nil { 113 | return err 114 | } 115 | errCh := make(chan error, 1) 116 | eventsCh, err := es.client.SubscribeToEventsStoreWithRequest(ctx, request, errCh) 117 | if err != nil { 118 | return err 119 | } 120 | go func() { 121 | for { 122 | select { 123 | case msg := <-eventsCh: 124 | onEvent(msg, nil) 125 | case err := <-errCh: 126 | onEvent(nil, err) 127 | case <-ctx.Done(): 128 | return 129 | } 130 | } 131 | }() 132 | return nil 133 | } 134 | 135 | // Create is a method that creates a new channel in the events store. 136 | func (es *EventsStoreClient) Create(ctx context.Context, channel string) error { 137 | return CreateChannel(ctx, es.client, es.client.opts.clientId, channel, "events_store") 138 | } 139 | 140 | // Delete is a method that deletes a channel from the events store. 141 | func (es *EventsStoreClient) Delete(ctx context.Context, channel string) error { 142 | return DeleteChannel(ctx, es.client, es.client.opts.clientId, channel, "events_store") 143 | } 144 | 145 | // List is a method that lists all channels in the events store. 146 | func (es *EventsStoreClient) List(ctx context.Context, search string) ([]*common.PubSubChannel, error) { 147 | return ListPubSubChannels(ctx, es.client, es.client.opts.clientId, "events_store", search) 148 | } 149 | 150 | // Close is a method that closes the client connection. 151 | func (es *EventsStoreClient) Close() error { 152 | if err := es.isClientReady(); err != nil { 153 | return err 154 | } 155 | return es.client.Close() 156 | } 157 | 158 | // isClientReady is a method that checks if the client is ready. 159 | func (es *EventsStoreClient) isClientReady() error { 160 | if es.client == nil { 161 | return fmt.Errorf("client is not initialized") 162 | } 163 | return nil 164 | } 165 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | Please visit our cookbook [repository](https://github.com/kubemq-io/go-sdk-cookbook) 3 | -------------------------------------------------------------------------------- /examples/cq/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go" 6 | "log" 7 | ) 8 | 9 | func createCommandsChannel() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | commandsClient, err := kubemq.NewCommandsClient(ctx, 13 | kubemq.WithAddress("localhost", 50000), 14 | kubemq.WithClientId("example")) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer func() { 19 | err := commandsClient.Close() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | }() 24 | if err := commandsClient.Create(ctx, "commands.A"); err != nil { 25 | log.Fatal(err) 26 | } 27 | } 28 | 29 | func createQueriesChannel() { 30 | ctx, cancel := context.WithCancel(context.Background()) 31 | defer cancel() 32 | queriesClient, err := kubemq.NewQueriesClient(ctx, 33 | kubemq.WithAddress("localhost", 50000), 34 | kubemq.WithClientId("example")) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | defer func() { 39 | err := queriesClient.Close() 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | }() 44 | if err := queriesClient.Create(ctx, "queries.A"); err != nil { 45 | log.Fatal(err) 46 | } 47 | } 48 | 49 | func main() { 50 | createCommandsChannel() 51 | createQueriesChannel() 52 | } 53 | -------------------------------------------------------------------------------- /examples/cq/delete/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go" 6 | "log" 7 | ) 8 | 9 | func deleteCommandsChannel() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | commandsClient, err := kubemq.NewCommandsClient(ctx, 13 | kubemq.WithAddress("localhost", 50000), 14 | kubemq.WithClientId("example")) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer func() { 19 | err := commandsClient.Close() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | }() 24 | if err := commandsClient.Delete(ctx, "commands.A"); err != nil { 25 | log.Fatal(err) 26 | } 27 | } 28 | 29 | func deleteQueriesChannel() { 30 | ctx, cancel := context.WithCancel(context.Background()) 31 | defer cancel() 32 | queriesClient, err := kubemq.NewQueriesClient(ctx, 33 | kubemq.WithAddress("localhost", 50000), 34 | kubemq.WithClientId("example")) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | defer func() { 39 | err := queriesClient.Close() 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | }() 44 | if err := queriesClient.Delete(ctx, "queries.A"); err != nil { 45 | log.Fatal(err) 46 | } 47 | } 48 | 49 | func main() { 50 | deleteCommandsChannel() 51 | deleteQueriesChannel() 52 | } 53 | -------------------------------------------------------------------------------- /examples/cq/functions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go" 6 | "log" 7 | "time" 8 | ) 9 | 10 | func sendReceiveCommands() { 11 | ctx, cancel := context.WithCancel(context.Background()) 12 | defer cancel() 13 | commandsClient, err := kubemq.NewCommandsClient(ctx, 14 | kubemq.WithAddress("localhost", 50000), 15 | kubemq.WithClientId("sendReceiveCommands")) 16 | 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | defer func() { 21 | err := commandsClient.Close() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | }() 26 | subRequest := &kubemq.CommandsSubscription{ 27 | Channel: "commands", 28 | ClientId: "", 29 | Group: "", 30 | } 31 | log.Println("subscribing to commands") 32 | err = commandsClient.Subscribe(ctx, subRequest, func(cmd *kubemq.CommandReceive, err error) { 33 | log.Println(cmd.String()) 34 | resp := &kubemq.Response{ 35 | RequestId: cmd.Id, 36 | ResponseTo: cmd.ResponseTo, 37 | Metadata: "some-metadata", 38 | ExecutedAt: time.Now(), 39 | } 40 | if err := commandsClient.Response(ctx, resp); err != nil { 41 | log.Fatal(err) 42 | } 43 | }) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | time.Sleep(1 * time.Second) 48 | log.Println("sending command") 49 | result, err := commandsClient.Send(ctx, kubemq.NewCommand(). 50 | SetChannel("commands"). 51 | SetMetadata("some-metadata"). 52 | SetBody([]byte("hello kubemq - sending command")). 53 | SetTimeout(time.Duration(10)*time.Second)) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | log.Println(result) 58 | } 59 | 60 | func sendReceiveQueries() { 61 | ctx, cancel := context.WithCancel(context.Background()) 62 | defer cancel() 63 | queriesClient, err := kubemq.NewQueriesClient(ctx, 64 | kubemq.WithAddress("localhost", 50000), 65 | kubemq.WithClientId("sendReceiveQueries")) 66 | 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | defer func() { 71 | err := queriesClient.Close() 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | }() 76 | subRequest := &kubemq.QueriesSubscription{ 77 | Channel: "queries", 78 | ClientId: "", 79 | Group: "", 80 | } 81 | log.Println("subscribing to queries") 82 | err = queriesClient.Subscribe(ctx, subRequest, func(query *kubemq.QueryReceive, err error) { 83 | log.Println(query.String()) 84 | resp := &kubemq.Response{ 85 | RequestId: query.Id, 86 | ResponseTo: query.ResponseTo, 87 | Metadata: "some-metadata", 88 | ExecutedAt: time.Now(), 89 | Body: []byte("hello kubemq - sending query response"), 90 | } 91 | if err := queriesClient.Response(ctx, resp); err != nil { 92 | log.Fatal(err) 93 | } 94 | }) 95 | if err != nil { 96 | log.Fatal(err) 97 | } 98 | time.Sleep(1 * time.Second) 99 | log.Println("sending query") 100 | result, err := queriesClient.Send(ctx, kubemq.NewQuery(). 101 | SetChannel("queries"). 102 | SetMetadata("some-metadata"). 103 | SetBody([]byte("hello kubemq - sending query")). 104 | SetTimeout(time.Duration(10)*time.Second)) 105 | if err != nil { 106 | log.Fatal(err) 107 | } 108 | log.Println(result) 109 | } 110 | func main() { 111 | sendReceiveCommands() 112 | sendReceiveQueries() 113 | } 114 | -------------------------------------------------------------------------------- /examples/cq/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go" 6 | "log" 7 | ) 8 | 9 | func listCommandsChannels() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | commandsClient, err := kubemq.NewCommandsClient(ctx, 13 | kubemq.WithAddress("localhost", 50000), 14 | kubemq.WithClientId("example")) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer func() { 19 | err := commandsClient.Close() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | }() 24 | channels, err := commandsClient.List(ctx, "") 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | for _, channel := range channels { 29 | log.Println(channel) 30 | } 31 | } 32 | 33 | func listQueriesChannels() { 34 | ctx, cancel := context.WithCancel(context.Background()) 35 | defer cancel() 36 | queriesClient, err := kubemq.NewQueriesClient(ctx, 37 | kubemq.WithAddress("localhost", 50000), 38 | kubemq.WithClientId("example")) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | defer func() { 43 | err := queriesClient.Close() 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | }() 48 | channels, err := queriesClient.List(ctx, "") 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | for _, channel := range channels { 53 | log.Println(channel) 54 | } 55 | } 56 | 57 | func main() { 58 | listCommandsChannels() 59 | listQueriesChannels() 60 | } 61 | -------------------------------------------------------------------------------- /examples/pubsub/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go" 6 | "log" 7 | ) 8 | 9 | func createEventsChannel() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | eventsClient, err := kubemq.NewEventsClient(ctx, 13 | kubemq.WithAddress("localhost", 50000), 14 | kubemq.WithClientId("events-channel-creator")) 15 | 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | defer func() { 20 | err := eventsClient.Close() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | }() 25 | if err := eventsClient.Create(ctx, "events-channel"); err != nil { 26 | log.Fatal(err) 27 | } 28 | } 29 | 30 | func createEventsStoreChannel() { 31 | ctx, cancel := context.WithCancel(context.Background()) 32 | defer cancel() 33 | eventsStoreClient, err := kubemq.NewEventsStoreClient(ctx, 34 | kubemq.WithAddress("localhost", 50000), 35 | kubemq.WithClientId("events-store-channel-creator")) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | defer func() { 40 | err := eventsStoreClient.Close() 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | }() 45 | if err := eventsStoreClient.Create(ctx, "events-store-channel"); err != nil { 46 | log.Fatal(err) 47 | } 48 | } 49 | 50 | func main() { 51 | createEventsChannel() 52 | createEventsStoreChannel() 53 | } 54 | -------------------------------------------------------------------------------- /examples/pubsub/delete/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go" 6 | "log" 7 | ) 8 | 9 | func deleteEventsChannel() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | eventsClient, err := kubemq.NewEventsClient(ctx, 13 | kubemq.WithAddress("localhost", 50000), 14 | kubemq.WithClientId("events-channel-delete")) 15 | 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | defer func() { 20 | err := eventsClient.Close() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | }() 25 | if err := eventsClient.Delete(ctx, "events-channel"); err != nil { 26 | log.Fatal(err) 27 | } 28 | } 29 | 30 | func deleteEventsStoreChannel() { 31 | ctx, cancel := context.WithCancel(context.Background()) 32 | defer cancel() 33 | eventsStoreClient, err := kubemq.NewEventsStoreClient(ctx, 34 | kubemq.WithAddress("localhost", 50000), 35 | kubemq.WithClientId("events-store-channel-delete")) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | defer func() { 40 | err := eventsStoreClient.Close() 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | }() 45 | if err := eventsStoreClient.Delete(ctx, "events-store-channel"); err != nil { 46 | log.Fatal(err) 47 | } 48 | } 49 | 50 | func main() { 51 | deleteEventsChannel() 52 | deleteEventsStoreChannel() 53 | } 54 | -------------------------------------------------------------------------------- /examples/pubsub/functions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go" 6 | "log" 7 | "time" 8 | ) 9 | 10 | func sendSubscribeEvents() { 11 | ctx, cancel := context.WithCancel(context.Background()) 12 | defer cancel() 13 | eventsClient, err := kubemq.NewEventsClient(ctx, 14 | kubemq.WithAddress("localhost", 50000), 15 | kubemq.WithClientId("events-send-subscribe")) 16 | 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | defer func() { 21 | err := eventsClient.Close() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | }() 26 | subReq := &kubemq.EventsSubscription{ 27 | Channel: "events-channel", 28 | Group: "", 29 | ClientId: "", 30 | } 31 | err = eventsClient.Subscribe(ctx, subReq, func(msg *kubemq.Event, err error) { 32 | log.Println(msg.String()) 33 | }) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | time.Sleep(300 * time.Second) 38 | 39 | err = eventsClient.Send(ctx, &kubemq.Event{ 40 | Channel: "events-channel", 41 | Metadata: "some-metadata", 42 | Body: []byte("hello kubemq - sending event"), 43 | }) 44 | 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | time.Sleep(1 * time.Second) 49 | } 50 | 51 | func sendSubscribeEventsStore() { 52 | ctx, cancel := context.WithCancel(context.Background()) 53 | defer cancel() 54 | eventsStoreClient, err := kubemq.NewEventsStoreClient(ctx, 55 | kubemq.WithAddress("localhost", 50000), 56 | kubemq.WithClientId("events-store-send-subscribe")) 57 | 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | defer func() { 62 | err := eventsStoreClient.Close() 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | }() 67 | subReq := &kubemq.EventsStoreSubscription{ 68 | Channel: "events-store-channel", 69 | Group: "", 70 | ClientId: "", 71 | SubscriptionType: kubemq.StartFromFirstEvent(), 72 | } 73 | err = eventsStoreClient.Subscribe(ctx, subReq, func(msg *kubemq.EventStoreReceive, err error) { 74 | log.Println(msg.String()) 75 | }) 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | time.Sleep(1 * time.Second) 80 | 81 | result, err := eventsStoreClient.Send(ctx, &kubemq.EventStore{ 82 | Channel: "events-store-channel", 83 | Metadata: "some-metadata", 84 | Body: []byte("hello kubemq - sending event store"), 85 | }) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | log.Println(result) 90 | time.Sleep(1 * time.Second) 91 | 92 | } 93 | func main() { 94 | sendSubscribeEvents() 95 | sendSubscribeEventsStore() 96 | } 97 | -------------------------------------------------------------------------------- /examples/pubsub/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go" 6 | "log" 7 | ) 8 | 9 | func listEventsChannels() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | eventsClient, err := kubemq.NewEventsClient(ctx, 13 | kubemq.WithAddress("localhost", 50000), 14 | kubemq.WithClientId("events-channel-lister")) 15 | 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | defer func() { 20 | err := eventsClient.Close() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | }() 25 | channels, err := eventsClient.List(ctx, "") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | for _, channel := range channels { 30 | log.Println(channel) 31 | } 32 | } 33 | 34 | func listEventsStoreChannel() { 35 | ctx, cancel := context.WithCancel(context.Background()) 36 | defer cancel() 37 | eventsStoreClient, err := kubemq.NewEventsStoreClient(ctx, 38 | kubemq.WithAddress("localhost", 50000), 39 | kubemq.WithClientId("events-store-channel-lister")) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | defer func() { 44 | err := eventsStoreClient.Close() 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | }() 49 | channels, err := eventsStoreClient.List(ctx, "") 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | for _, channel := range channels { 54 | log.Println(channel) 55 | } 56 | } 57 | 58 | func main() { 59 | listEventsChannels() 60 | listEventsStoreChannel() 61 | } 62 | -------------------------------------------------------------------------------- /examples/queues/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go/queues_stream" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | queuesClient, err := queues_stream.NewQueuesStreamClient(ctx, 13 | queues_stream.WithAddress("localhost", 50000), 14 | queues_stream.WithClientId("example")) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer func() { 19 | err := queuesClient.Close() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | }() 24 | if err := queuesClient.Create(ctx, "queues.A"); err != nil { 25 | log.Fatal(err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/queues/delete/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go/queues_stream" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | queuesClient, err := queues_stream.NewQueuesStreamClient(ctx, 13 | queues_stream.WithAddress("localhost", 50000), 14 | queues_stream.WithClientId("example")) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer func() { 19 | err := queuesClient.Close() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | }() 24 | if err := queuesClient.Delete(ctx, "queues.A"); err != nil { 25 | log.Fatal(err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/queues/list/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go/queues_stream" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithCancel(context.Background()) 11 | defer cancel() 12 | queuesClient, err := queues_stream.NewQueuesStreamClient(ctx, 13 | queues_stream.WithAddress("localhost", 50000), 14 | queues_stream.WithClientId("example")) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer func() { 19 | err := queuesClient.Close() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | }() 24 | channels, err := queuesClient.List(ctx, "") 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | for _, channel := range channels { 29 | log.Println(channel) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/queues/operations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/kubemq-io/kubemq-go/queues_stream" 6 | "log" 7 | "time" 8 | ) 9 | 10 | func sendAndReceive() { 11 | ctx, cancel := context.WithCancel(context.Background()) 12 | defer cancel() 13 | queuesClient, err := queues_stream.NewQueuesStreamClient(ctx, 14 | queues_stream.WithAddress("localhost", 50000), 15 | queues_stream.WithClientId("example")) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | defer func() { 20 | err := queuesClient.Close() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | }() 25 | msg := queues_stream.NewQueueMessage(). 26 | SetId("message_id"). 27 | SetChannel("sendAndReceive"). 28 | SetMetadata("some-metadata"). 29 | SetBody([]byte("hello world from KubeMQ queue")). 30 | SetTags(map[string]string{ 31 | "key1": "value1", 32 | "key2": "value2", 33 | }). 34 | SetPolicyDelaySeconds(1). 35 | SetPolicyExpirationSeconds(10). 36 | SetPolicyMaxReceiveCount(3). 37 | SetPolicyMaxReceiveQueue("dlq") 38 | result, err := queuesClient.Send(ctx, msg) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | log.Println(result) 43 | pollRequest := queues_stream.NewPollRequest(). 44 | SetChannel("sendAndReceive"). 45 | SetMaxItems(1). 46 | SetWaitTimeout(10). 47 | SetAutoAck(true) 48 | msgs, err := queuesClient.Poll(ctx, pollRequest) 49 | //if err != nil { 50 | // log.Fatal(err) 51 | //} 52 | //AckAll - Acknowledge all messages 53 | //if err := msgs.AckAll(); err != nil { 54 | // log.Fatal(err) 55 | //} 56 | 57 | //NackAll - Not Acknowledge all messages 58 | //if err := msgs.NAckAll(); err != nil { 59 | // log.Fatal(err) 60 | //} 61 | 62 | // RequeueAll - Requeue all messages 63 | //if err := msgs.ReQueueAll("requeue-queue-channel"); err != nil { 64 | // log.Fatal(err) 65 | //} 66 | 67 | for _, msg := range msgs.Messages { 68 | log.Println(msg.String()) 69 | 70 | // Ack - Acknowledge message 71 | if err := msg.Ack(); err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | // Nack - Not Acknowledge message 76 | //if err := msg.NAck(); err != nil { 77 | // log.Fatal(err) 78 | //} 79 | 80 | // Requeue - Requeue message 81 | //if err := msg.ReQueue("requeue-queue-channel"); err != nil { 82 | // log.Fatal(err) 83 | //} 84 | } 85 | 86 | } 87 | func sendAndReceiveWithVisibility() { 88 | ctx, cancel := context.WithCancel(context.Background()) 89 | defer cancel() 90 | queuesClient, err := queues_stream.NewQueuesStreamClient(ctx, 91 | queues_stream.WithAddress("localhost", 50000), 92 | queues_stream.WithClientId("example")) 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | defer func() { 97 | err := queuesClient.Close() 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | }() 102 | msg := queues_stream.NewQueueMessage(). 103 | SetId("message_id"). 104 | SetChannel("sendAndReceiveWithVisibility"). 105 | SetMetadata("some-metadata"). 106 | SetBody([]byte("hello world from KubeMQ queue - with visibility")) 107 | 108 | result, err := queuesClient.Send(ctx, msg) 109 | if err != nil { 110 | log.Fatal(err) 111 | } 112 | log.Println(result) 113 | pollRequest := queues_stream.NewPollRequest(). 114 | SetChannel("sendAndReceiveWithVisibility"). 115 | SetMaxItems(1). 116 | SetWaitTimeout(10). 117 | SetVisibilitySeconds(2) 118 | msgs, err := queuesClient.Poll(ctx, pollRequest) 119 | for _, msg := range msgs.Messages { 120 | log.Println(msg.String()) 121 | if err := msg.Ack(); err != nil { 122 | log.Fatal(err) 123 | } 124 | } 125 | } 126 | 127 | func sendAndReceiveWithVisibilityExpiration() { 128 | ctx, cancel := context.WithCancel(context.Background()) 129 | defer cancel() 130 | queuesClient, err := queues_stream.NewQueuesStreamClient(ctx, 131 | queues_stream.WithAddress("localhost", 50000), 132 | queues_stream.WithClientId("example")) 133 | if err != nil { 134 | log.Fatal(err) 135 | } 136 | defer func() { 137 | err := queuesClient.Close() 138 | if err != nil { 139 | log.Fatal(err) 140 | } 141 | }() 142 | msg := queues_stream.NewQueueMessage(). 143 | SetId("message_id"). 144 | SetChannel("sendAndReceiveWithVisibility"). 145 | SetMetadata("some-metadata"). 146 | SetBody([]byte("hello world from KubeMQ queue - with visibility")) 147 | 148 | result, err := queuesClient.Send(ctx, msg) 149 | if err != nil { 150 | log.Fatal(err) 151 | } 152 | log.Println(result) 153 | pollRequest := queues_stream.NewPollRequest(). 154 | SetChannel("sendAndReceiveWithVisibility"). 155 | SetMaxItems(1). 156 | SetWaitTimeout(10). 157 | SetVisibilitySeconds(2) 158 | msgs, err := queuesClient.Poll(ctx, pollRequest) 159 | for _, msg := range msgs.Messages { 160 | log.Println(msg.String()) 161 | log.Println("Received message, waiting 3 seconds before ack") 162 | time.Sleep(3 * time.Second) 163 | if err := msg.Ack(); err != nil { 164 | log.Fatal(err) 165 | } 166 | } 167 | 168 | } 169 | func sendAndReceiveWithVisibilityExtension() { 170 | ctx, cancel := context.WithCancel(context.Background()) 171 | defer cancel() 172 | queuesClient, err := queues_stream.NewQueuesStreamClient(ctx, 173 | queues_stream.WithAddress("localhost", 50000), 174 | queues_stream.WithClientId("example")) 175 | if err != nil { 176 | log.Fatal(err) 177 | } 178 | defer func() { 179 | err := queuesClient.Close() 180 | if err != nil { 181 | log.Fatal(err) 182 | } 183 | }() 184 | msg := queues_stream.NewQueueMessage(). 185 | SetId("message_id"). 186 | SetChannel("sendAndReceiveWithVisibility"). 187 | SetMetadata("some-metadata"). 188 | SetBody([]byte("hello world from KubeMQ queue - with visibility")) 189 | 190 | result, err := queuesClient.Send(ctx, msg) 191 | if err != nil { 192 | log.Fatal(err) 193 | } 194 | log.Println(result) 195 | pollRequest := queues_stream.NewPollRequest(). 196 | SetChannel("sendAndReceiveWithVisibility"). 197 | SetMaxItems(1). 198 | SetWaitTimeout(10). 199 | SetVisibilitySeconds(2) 200 | msgs, err := queuesClient.Poll(ctx, pollRequest) 201 | for _, msg := range msgs.Messages { 202 | log.Println(msg.String()) 203 | log.Println("Received message, waiting 1 seconds before ack") 204 | time.Sleep(1 * time.Second) 205 | log.Println("Extending visibility for 3 seconds and waiting 2 seconds before ack") 206 | if err := msg.ExtendVisibility(3); err != nil { 207 | log.Fatal(err) 208 | } 209 | time.Sleep(2 * time.Second) 210 | if err := msg.Ack(); err != nil { 211 | log.Fatal(err) 212 | } 213 | } 214 | } 215 | func main() { 216 | sendAndReceive() 217 | sendAndReceiveWithVisibility() 218 | sendAndReceiveWithVisibilityExpiration() 219 | sendAndReceiveWithVisibilityExtension() 220 | } 221 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubemq-io/kubemq-go 2 | 3 | go 1.22 4 | 5 | toolchain go1.23.0 6 | 7 | require ( 8 | github.com/go-resty/resty/v2 v2.12.0 9 | github.com/gorilla/websocket v1.5.1 10 | github.com/kubemq-io/protobuf v1.3.1 11 | github.com/nats-io/nuid v1.0.1 12 | github.com/stretchr/testify v1.9.0 13 | go.opencensus.io v0.24.0 14 | go.uber.org/atomic v1.11.0 15 | google.golang.org/grpc v1.69.2 16 | ) 17 | 18 | require ( 19 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 22 | github.com/golang/protobuf v1.5.4 // indirect 23 | github.com/kr/pretty v0.3.1 // indirect 24 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 25 | golang.org/x/net v0.33.0 // indirect 26 | golang.org/x/sys v0.28.0 // indirect 27 | golang.org/x/text v0.21.0 // indirect 28 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect 29 | google.golang.org/protobuf v1.35.1 // indirect 30 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 31 | gopkg.in/yaml.v3 v3.0.1 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 10 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 12 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 13 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 14 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 15 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 16 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 17 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 18 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 19 | github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= 20 | github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= 21 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 22 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 23 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 24 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 25 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 26 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 27 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 28 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 29 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 30 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 31 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 32 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 33 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 34 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 35 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 36 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 37 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 38 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 39 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 40 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 41 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 42 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 43 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 44 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 45 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 46 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 47 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 48 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 49 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 50 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= 51 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 52 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 53 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 54 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 55 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 56 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 57 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 58 | github.com/kubemq-io/protobuf v1.3.1 h1:b4QcnpujV8U3go8pa2+FTESl6ygU6hY8APYibRtyemo= 59 | github.com/kubemq-io/protobuf v1.3.1/go.mod h1:mzbGBI05R+GhFLD520xweEIvDM+m4nI7ruJDhgEncas= 60 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= 61 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 62 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 63 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 64 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 65 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 66 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 67 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 68 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 69 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 70 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 71 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 72 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 73 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 74 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 75 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 76 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 77 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 78 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 79 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 80 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 81 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 82 | go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= 83 | go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= 84 | go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= 85 | go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= 86 | go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= 87 | go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= 88 | go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= 89 | go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= 90 | go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= 91 | go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= 92 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 93 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 94 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 95 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 96 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 97 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 98 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 99 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 100 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 101 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 102 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 103 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 104 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 105 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 106 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 107 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 108 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 109 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 110 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 111 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 112 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 113 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 114 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 115 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 116 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 117 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 118 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 119 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 120 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 121 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 122 | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 123 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 124 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 125 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 126 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 127 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 128 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 129 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 130 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 131 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 132 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 133 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 134 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 135 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 136 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 137 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 139 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 140 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 141 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 142 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 143 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 144 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 145 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 146 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 147 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 148 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 149 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 150 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 151 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 152 | golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 153 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 154 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 155 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 156 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 157 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 158 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 159 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 160 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 161 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 162 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 163 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 164 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 165 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 166 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 167 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 168 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 169 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 170 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 171 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 172 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 173 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 174 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 175 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 176 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 177 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 178 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 179 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 180 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 181 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 182 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= 183 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 184 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 185 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 186 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 187 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 188 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 189 | google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= 190 | google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 191 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 192 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 193 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 194 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 195 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 196 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 197 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 198 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 199 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 200 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 201 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 202 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 203 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 204 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 205 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 206 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 207 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 208 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 209 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 210 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | const kubeMQAuthTokenHeader = "authorization" 10 | 11 | type Option interface { 12 | apply(*Options) 13 | } 14 | type TransportType int 15 | 16 | const ( 17 | TransportTypeGRPC TransportType = iota 18 | TransportTypeRest 19 | ) 20 | 21 | type Options struct { 22 | host string 23 | port int 24 | isSecured bool 25 | certFile string 26 | certData string 27 | serverOverrideDomain string 28 | authToken string 29 | clientId string 30 | receiveBufferSize int 31 | defaultChannel string 32 | defaultCacheTTL time.Duration 33 | transportType TransportType 34 | restUri string 35 | webSocketUri string 36 | autoReconnect bool 37 | reconnectInterval time.Duration 38 | maxReconnect int 39 | checkConnection bool 40 | } 41 | 42 | type funcOptions struct { 43 | fn func(*Options) 44 | } 45 | 46 | func (fo *funcOptions) apply(o *Options) { 47 | fo.fn(o) 48 | } 49 | 50 | func newFuncOption(f func(*Options)) *funcOptions { 51 | return &funcOptions{ 52 | fn: f, 53 | } 54 | } 55 | 56 | // WithAddress - set host and port address of KubeMQ server 57 | func WithAddress(host string, port int) Option { 58 | return newFuncOption(func(o *Options) { 59 | o.host = host 60 | o.port = port 61 | 62 | }) 63 | } 64 | 65 | // WithUriAddress - set uri address of KubeMQ server 66 | func WithUri(uri string) Option { 67 | return newFuncOption(func(o *Options) { 68 | o.restUri = uri 69 | o.webSocketUri = strings.Replace(uri, "http", "ws", 1) 70 | }) 71 | } 72 | 73 | // WithCredentials - set secured TLS credentials from the input certificate file for client. 74 | // serverNameOverride is for testing only. If set to a non empty string, 75 | // it will override the virtual host name of authority (e.g. :authority header field) in requests. 76 | func WithCredentials(certFile, serverOverrideDomain string) Option { 77 | return newFuncOption(func(o *Options) { 78 | o.isSecured = true 79 | o.certFile = certFile 80 | o.serverOverrideDomain = serverOverrideDomain 81 | }) 82 | } 83 | 84 | // WithCertificate - set secured TLS credentials from the input certificate data for client. 85 | // serverNameOverride is for testing only. If set to a non empty string, 86 | // it will override the virtual host name of authority (e.g. :authority header field) in requests. 87 | func WithCertificate(certData, serverOverrideDomain string) Option { 88 | return newFuncOption(func(o *Options) { 89 | o.isSecured = true 90 | o.certData = certData 91 | o.serverOverrideDomain = serverOverrideDomain 92 | }) 93 | } 94 | 95 | // WithAuthToken - set KubeMQ JWT Auth token to be used for KubeMQ connection 96 | func WithAuthToken(token string) Option { 97 | return newFuncOption(func(o *Options) { 98 | o.authToken = token 99 | }) 100 | } 101 | 102 | // WithClientId - set client id to be used in all functions call with this client - mandatory 103 | func WithClientId(id string) Option { 104 | return newFuncOption(func(o *Options) { 105 | o.clientId = id 106 | }) 107 | } 108 | 109 | // WithReceiveBufferSize - set length of buffered channel to be set in all subscriptions 110 | func WithReceiveBufferSize(size int) Option { 111 | return newFuncOption(func(o *Options) { 112 | o.receiveBufferSize = size 113 | }) 114 | } 115 | 116 | // WithDefaultChannel - set default channel for any outbound requests 117 | func WithDefaultChannel(channel string) Option { 118 | return newFuncOption(func(o *Options) { 119 | o.defaultChannel = channel 120 | }) 121 | } 122 | 123 | // WithDefaultCacheTTL - set default cache time to live for any query requests with any CacheKey set value 124 | func WithDefaultCacheTTL(ttl time.Duration) Option { 125 | return newFuncOption(func(o *Options) { 126 | o.defaultCacheTTL = ttl 127 | }) 128 | } 129 | 130 | // WithAutoReconnect - set automatic reconnection in case of lost connectivity to server 131 | func WithAutoReconnect(value bool) Option { 132 | return newFuncOption(func(o *Options) { 133 | o.autoReconnect = value 134 | }) 135 | } 136 | 137 | // WithReconnectInterval - set reconnection interval duration, default is 5 seconds 138 | func WithReconnectInterval(duration time.Duration) Option { 139 | return newFuncOption(func(o *Options) { 140 | o.reconnectInterval = duration 141 | }) 142 | } 143 | 144 | // WithMaxReconnects - set max reconnects before return error, default 0, never. 145 | func WithMaxReconnects(value int) Option { 146 | return newFuncOption(func(o *Options) { 147 | o.maxReconnect = value 148 | }) 149 | } 150 | 151 | // WithTransportType - set client transport type, currently GRPC or Rest 152 | func WithTransportType(transportType TransportType) Option { 153 | return newFuncOption(func(o *Options) { 154 | o.transportType = transportType 155 | }) 156 | } 157 | 158 | // WithCheckConnection - set server connectivity on client create 159 | func WithCheckConnection(value bool) Option { 160 | return newFuncOption(func(o *Options) { 161 | o.checkConnection = value 162 | }) 163 | } 164 | 165 | func GetDefaultOptions() *Options { 166 | return &Options{ 167 | host: "", 168 | port: 0, 169 | isSecured: false, 170 | certFile: "", 171 | certData: "", 172 | serverOverrideDomain: "", 173 | authToken: "", 174 | clientId: "ClientId", 175 | receiveBufferSize: 10, 176 | defaultChannel: "", 177 | defaultCacheTTL: time.Minute * 15, 178 | transportType: 0, 179 | restUri: "", 180 | webSocketUri: "", 181 | autoReconnect: false, 182 | reconnectInterval: 5 * time.Second, 183 | maxReconnect: 0, 184 | checkConnection: false, 185 | } 186 | } 187 | 188 | func (o *Options) Validate() error { 189 | switch o.transportType { 190 | case TransportTypeGRPC: 191 | if o.host == "" { 192 | return errors.New("invalid host") 193 | } 194 | if o.port <= 0 { 195 | return errors.New("invalid port") 196 | } 197 | case TransportTypeRest: 198 | if o.restUri == "" { 199 | return errors.New("invalid address uri") 200 | } 201 | default: 202 | return errors.New("no transport type was set") 203 | } 204 | return nil 205 | } 206 | -------------------------------------------------------------------------------- /pkg/uuid/uuid.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | ) 7 | 8 | // UUID Represents a UUID 9 | type UUID [16]byte 10 | 11 | func (u *UUID) string() string { 12 | result := hex.EncodeToString(u[:]) 13 | result = result[:8] + "-" + result[8:12] + "-" + result[12:16] + "-" + result[16:20] + "-" + result[20:] 14 | return result 15 | } 16 | 17 | func New() string { 18 | bytes := make([]byte, 16) 19 | _, _ = rand.Read(bytes) 20 | 21 | result := &UUID{} 22 | for i, v := range bytes { 23 | result[i] = v 24 | } 25 | 26 | result.setVersion(4) 27 | result.setVariant() 28 | return result.string() 29 | } 30 | 31 | func (u *UUID) setVersion(v byte) { 32 | u[6] = (v << 4) | (u[6] & 0xf) 33 | } 34 | 35 | func (u *UUID) setVariant() { 36 | // Clear the first two bits of the byte 37 | u[8] = u[8] & 0x3f 38 | // Set the first two bits of the byte to 10 39 | u[8] = u[8] | 0x80 40 | } 41 | -------------------------------------------------------------------------------- /queries_client.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/kubemq-io/kubemq-go/common" 7 | ) 8 | 9 | // QueriesClient represents a client for making queries to a server. 10 | // It contains a reference to the underlying client that handles the 11 | // communication with the server. 12 | type QueriesClient struct { 13 | client *Client 14 | } 15 | 16 | // QueriesSubscription represents a subscription to queries requests by channel and group 17 | type QueriesSubscription struct { 18 | Channel string 19 | Group string 20 | ClientId string 21 | } 22 | 23 | // Complete updates the ClientId of the QueriesSubscription if it is empty, 24 | // using the clientId value from the provided Options. 25 | // It returns a pointer to the modified QueriesSubscription. 26 | func (qs *QueriesSubscription) Complete(opts *Options) *QueriesSubscription { 27 | if qs.ClientId == "" { 28 | qs.ClientId = opts.clientId 29 | } 30 | return qs 31 | } 32 | 33 | // Validate checks if a queries subscription is valid. It returns an error if any 34 | // of the required fields are empty. 35 | func (qs *QueriesSubscription) Validate() error { 36 | if qs.Channel == "" { 37 | return fmt.Errorf("queries subscription must have a channel") 38 | } 39 | if qs.ClientId == "" { 40 | return fmt.Errorf("queries subscription must have a clientId") 41 | } 42 | return nil 43 | } 44 | 45 | // NewQueriesClient creates a new instance of QueriesClient by calling NewClient function and returning QueriesClient 46 | // with the newly created Client instance. It receives a context and an optional list of options as arguments 47 | // and returns a pointer to QueriesClient and an error. 48 | func NewQueriesClient(ctx context.Context, op ...Option) (*QueriesClient, error) { 49 | client, err := NewClient(ctx, op...) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &QueriesClient{ 54 | client: client, 55 | }, nil 56 | } 57 | 58 | // Send sends a query request using the provided context. It checks if the client is ready, 59 | // sets the transport from the client, and calls the Send method on the client with the request. 60 | // It returns the query response and an error, if any. 61 | // 62 | // The following fields in the request are required: 63 | // - transport (set from the client) 64 | // 65 | // Example usage: 66 | // 67 | // request := &Query{ 68 | // Id: "123", 69 | // Channel: "channel1", 70 | // Metadata: "metadata", 71 | // Body: []byte("query body"), 72 | // Timeout: time.Second, 73 | // ClientId: "client1", 74 | // CacheKey: "cacheKey", 75 | // CacheTTL: time.Minute, 76 | // Tags: map[string]string{"tag1": "value1"}, 77 | // } 78 | // response, err := client.Send(ctx, request) 79 | func (q *QueriesClient) Send(ctx context.Context, request *Query) (*QueryResponse, error) { 80 | if err := q.isClientReady(); err != nil { 81 | return nil, err 82 | } 83 | request.transport = q.client.transport 84 | return q.client.SetQuery(request).Send(ctx) 85 | } 86 | 87 | // Response sends a response to a command or query request. 88 | // 89 | // The response must have a corresponding requestId and response channel, 90 | // which are set using SetRequestId and SetResponseTo methods, respectively. 91 | // The requestId is mandatory, while the response channel is received from 92 | // either CommandReceived or QueryReceived objects. 93 | // 94 | // Additional optional attributes that can be set for the response include: 95 | // - Metadata: SetMetadata should be used to set metadata for a query response only. 96 | // - Body: SetBody should be used to set the body for a query response only. 97 | // - Tags: SetTags can be used to set tags for the response. 98 | // - ClientId: SetClientId can be used to set the clientId for the response. If not set, 99 | // the default clientId will be used. 100 | // - Error: SetError can be used to set an error when executing a command or query. 101 | // - ExecutedAt: SetExecutedAt can be used to set the execution time for a command or query. 102 | // - Trace: AddTrace can be used to add tracing support to the response. 103 | // 104 | // Once all the necessary attributes are set, call the Send method to send the response. 105 | // 106 | // Example: 107 | // 108 | // resp := &Response{} 109 | // resp.SetRequestId("12345") 110 | // resp.SetResponseTo("channel-name") 111 | // resp.SetMetadata("metadata") 112 | // resp.SetBody([]byte("response-body")) 113 | // resp.SetTags(map[string]string{"tag1": "value1", "tag2": "value2"}) 114 | // resp.SetClientId("client-id") 115 | // resp.SetError(errors.New("some error")) 116 | // resp.SetExecutedAt(time.Now()) 117 | // resp.AddTrace("trace-name") 118 | // 119 | // err := resp.Send(ctx) 120 | // if err != nil { 121 | // log.Println("Failed to send response:", err) 122 | // } 123 | func (q *QueriesClient) Response(ctx context.Context, response *Response) error { 124 | if err := q.isClientReady(); err != nil { 125 | return err 126 | } 127 | response.transport = q.client.transport 128 | return q.client.SetResponse(response).Send(ctx) 129 | } 130 | 131 | // Subscribe is a method of QueriesClient that allows a client to subscribe to queries. 132 | // It takes a context, a QueriesSubscription request, and a callback function onQueryReceive. 133 | // The context is used for cancellation, timing out, and passing values between middleware. 134 | // The QueriesSubscription defines the channel, group, and clientId for the subscription. 135 | // The onQueryReceive callback function will be called when a query is received or an error occurs. 136 | // The method returns an error if the client is not ready, if the onQueryReceive function is nil, 137 | // or if the QueriesSubscription request is invalid. 138 | // The method creates a channel for receiving errors, subscribes to queries with the given request, 139 | // and starts a goroutine that continuously listens for new queries or errors on the channel. 140 | // When a query is received, it is passed to the onQueryReceive function with a nil error. 141 | // When an error is received, it is passed to the onQueryReceive function with a nil query. 142 | // If the context is canceled, the goroutine returns. 143 | // The method returns with nil if the subscription is successfully set up. 144 | func (q *QueriesClient) Subscribe(ctx context.Context, request *QueriesSubscription, onQueryReceive func(query *QueryReceive, err error)) error { 145 | if err := q.isClientReady(); err != nil { 146 | return err 147 | } 148 | if onQueryReceive == nil { 149 | return fmt.Errorf("queries request subscription callback function is required") 150 | } 151 | if err := request.Complete(q.client.opts).Validate(); err != nil { 152 | return err 153 | } 154 | 155 | errCh := make(chan error, 1) 156 | queriesCh, err := q.client.SubscribeToQueriesWithRequest(ctx, request, errCh) 157 | if err != nil { 158 | return err 159 | } 160 | go func() { 161 | for { 162 | select { 163 | case query := <-queriesCh: 164 | onQueryReceive(query, nil) 165 | case err := <-errCh: 166 | onQueryReceive(nil, err) 167 | case <-ctx.Done(): 168 | return 169 | } 170 | } 171 | }() 172 | return nil 173 | } 174 | 175 | // Create creates a new channel in the QueriesClient with the given channel name. 176 | // 177 | // Parameters: 178 | // - ctx (context.Context): The context for the request. 179 | // - channel (string): The name of the channel to create. 180 | // 181 | // Returns: 182 | // - error: An error if the channel creation fails. 183 | func (q *QueriesClient) Create(ctx context.Context, channel string) error { 184 | return CreateChannel(ctx, q.client, q.client.opts.clientId, channel, "queries") 185 | } 186 | 187 | // Delete deletes a channel. 188 | // 189 | // The method receives a context and the channel name to be deleted. It invokes the DeleteChannel function passing the 190 | // received channel name as well as the clientId and channelType. DeleteChannel creates a new Query instance and sets 191 | // the necessary properties such as the channel, clientId, metadata, tags, and timeout. It then calls the Send method 192 | // of the client to send the delete channel request. If an error occurs during the request execution, it returns an 193 | // error. If the response contains an error message, it returns an error. Otherwise, it returns nil, indicating the 194 | // channel was successfully deleted. 195 | func (q *QueriesClient) Delete(ctx context.Context, channel string) error { 196 | return DeleteChannel(ctx, q.client, q.client.opts.clientId, channel, "queries") 197 | } 198 | 199 | // List retrieves a list of channels with the specified search criteria. 200 | // It returns a slice of *common.CQChannel and an error. 201 | // The search criteria is passed as a string parameter. 202 | // The Channels are retrieved using the ListCQChannels function, passing the context, client, client ID, channel type, and search criteria. 203 | // If an error occurs during the retrieval, it is returned. 204 | // If the retrieval is successful, the data is decoded into a slice of *common.CQChannel using the DecodeCQChannelList function. 205 | // The decoded data and any error are returned. 206 | func (q *QueriesClient) List(ctx context.Context, search string) ([]*common.CQChannel, error) { 207 | return ListCQChannels(ctx, q.client, q.client.opts.clientId, "queries", search) 208 | } 209 | 210 | // Close closes the QueriesClient's underlying client connection. 211 | // This method returns an error if the client is not initialized. 212 | func (q *QueriesClient) Close() error { 213 | return q.client.Close() 214 | } 215 | 216 | // isClientReady checks if the client is properly initialized. 217 | // It returns an error if the client is nil, indicating that it is not initialized. 218 | func (q *QueriesClient) isClientReady() error { 219 | if q.client == nil { 220 | return fmt.Errorf("client is not initialized") 221 | } 222 | return nil 223 | } 224 | -------------------------------------------------------------------------------- /query.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | type Query struct { 10 | Id string 11 | Channel string 12 | Metadata string 13 | Body []byte 14 | Timeout time.Duration 15 | ClientId string 16 | CacheKey string 17 | CacheTTL time.Duration 18 | Tags map[string]string 19 | transport Transport 20 | trace *Trace 21 | } 22 | 23 | func NewQuery() *Query { 24 | return &Query{} 25 | } 26 | 27 | // SetId - set query requestId, otherwise new random uuid will be set 28 | func (q *Query) SetId(id string) *Query { 29 | q.Id = id 30 | return q 31 | } 32 | 33 | // SetClientId - set query ClientId - mandatory if default client was not set 34 | func (q *Query) SetClientId(clientId string) *Query { 35 | q.ClientId = clientId 36 | return q 37 | } 38 | 39 | // SetChannel - set query channel - mandatory if default channel was not set 40 | func (q *Query) SetChannel(channel string) *Query { 41 | q.Channel = channel 42 | return q 43 | } 44 | 45 | // SetMetadata - set query metadata - mandatory if body field is empty 46 | func (q *Query) SetMetadata(metadata string) *Query { 47 | q.Metadata = metadata 48 | return q 49 | } 50 | 51 | // SetBody - set query body - mandatory if metadata field is empty 52 | func (q *Query) SetBody(body []byte) *Query { 53 | q.Body = body 54 | return q 55 | } 56 | 57 | // SetTags - set key value tags to query message 58 | func (q *Query) SetTags(tags map[string]string) *Query { 59 | q.Tags = map[string]string{} 60 | for key, value := range tags { 61 | q.Tags[key] = value 62 | } 63 | return q 64 | } 65 | 66 | // AddTag - add key value tags to query message 67 | func (q *Query) AddTag(key, value string) *Query { 68 | if q.Tags == nil { 69 | q.Tags = map[string]string{} 70 | } 71 | q.Tags[key] = value 72 | return q 73 | } 74 | 75 | // SetTimeout - set timeout for query to be returned. if timeout expired , send query will result with an error 76 | func (q *Query) SetTimeout(timeout time.Duration) *Query { 77 | q.Timeout = timeout 78 | return q 79 | } 80 | 81 | // SetCacheKey - set cache key to retrieve already stored query response, otherwise the response for this query will be stored in cache for future query requests 82 | func (q *Query) SetCacheKey(cacheKey string) *Query { 83 | q.CacheKey = cacheKey 84 | return q 85 | } 86 | 87 | // SetCacheTTL - set cache time to live for the this query cache key response to be retrieved already stored query response, if not set default cacheTTL will be set 88 | func (q *Query) SetCacheTTL(ttl time.Duration) *Query { 89 | q.CacheTTL = ttl 90 | return q 91 | } 92 | 93 | // Send - sending query request , waiting for response or timeout 94 | func (q *Query) Send(ctx context.Context) (*QueryResponse, error) { 95 | if q.transport == nil { 96 | return nil, ErrNoTransportDefined 97 | } 98 | return q.transport.SendQuery(ctx, q) 99 | } 100 | 101 | // AddTrace - add tracing support to query 102 | func (q *Query) AddTrace(name string) *Trace { 103 | q.trace = CreateTrace(name) 104 | return q.trace 105 | } 106 | 107 | type QueryReceive struct { 108 | Id string 109 | Channel string 110 | ClientId string 111 | Metadata string 112 | Body []byte 113 | ResponseTo string 114 | Tags map[string]string 115 | } 116 | 117 | func (qr *QueryReceive) String() string { 118 | return fmt.Sprintf("Id: %s, ClientId: %s, Channel: %s, Metadata: %s, Body: %s, ResponseTo: %s, Tags: %v", qr.Id, qr.ClientId, qr.Channel, qr.Metadata, string(qr.Body), qr.ResponseTo, qr.Tags) 119 | } 120 | 121 | type QueryResponse struct { 122 | QueryId string 123 | Executed bool 124 | ExecutedAt time.Time 125 | Metadata string 126 | ResponseClientId string 127 | Body []byte 128 | CacheHit bool 129 | Error string 130 | Tags map[string]string 131 | } 132 | 133 | func (qr *QueryResponse) String() string { 134 | return fmt.Sprintf("QueryId: %s, Executed: %v, ExecutedAt: %s, Metadata: %s, ResponseClientId: %s, Body: %s, CacheHit: %v, Error: %s, Tags: %v", qr.QueryId, qr.Executed, qr.ExecutedAt.String(), qr.Metadata, qr.ResponseClientId, string(qr.Body), qr.CacheHit, qr.Error, qr.Tags) 135 | } 136 | -------------------------------------------------------------------------------- /queues_stream/ack_all_messages.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubemq-io/kubemq-go/pkg/uuid" 6 | ) 7 | 8 | type AckAllRequest struct { 9 | requestID string 10 | ClientID string 11 | Channel string 12 | WaitTimeSeconds int32 13 | } 14 | 15 | func NewAckAllRequest() *AckAllRequest { 16 | return &AckAllRequest{ 17 | requestID: uuid.New(), 18 | ClientID: "", 19 | Channel: "", 20 | WaitTimeSeconds: 0, 21 | } 22 | } 23 | 24 | func (req *AckAllRequest) SetClientId(clientId string) *AckAllRequest { 25 | req.ClientID = clientId 26 | return req 27 | } 28 | 29 | // SetChannel - set ack all queue message request channel - mandatory if default channel was not set 30 | func (req *AckAllRequest) SetChannel(channel string) *AckAllRequest { 31 | req.Channel = channel 32 | return req 33 | } 34 | 35 | // SetWaitTimeSeconds - set ack all queue message request wait timout 36 | func (req *AckAllRequest) SetWaitTimeSeconds(wait int) *AckAllRequest { 37 | req.WaitTimeSeconds = int32(wait) 38 | return req 39 | } 40 | 41 | func (req *AckAllRequest) validateAndComplete(clientId string) error { 42 | if req.ClientID == "" { 43 | req.ClientID = clientId 44 | } 45 | if req.Channel == "" { 46 | return fmt.Errorf("ack all must have a channel") 47 | } 48 | if req.ClientID == "" { 49 | return fmt.Errorf("ack all must have a clientId") 50 | } 51 | 52 | if req.WaitTimeSeconds <= 0 { 53 | return fmt.Errorf("queues subscription must have a wait time seconds >0") 54 | } 55 | 56 | return nil 57 | } 58 | 59 | type AckAllResponse struct { 60 | RequestID string 61 | AffectedMessages uint64 62 | IsError bool 63 | Error string 64 | } 65 | -------------------------------------------------------------------------------- /queues_stream/client.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/kubemq-io/kubemq-go" 7 | "github.com/kubemq-io/kubemq-go/common" 8 | "sync" 9 | "time" 10 | 11 | "github.com/kubemq-io/kubemq-go/pkg/uuid" 12 | pb "github.com/kubemq-io/protobuf/go" 13 | ) 14 | 15 | // QueuesStreamClient is a client for streaming queues operations. 16 | // It manages the connection to the Kubemq server and provides methods for interacting with queues. 17 | type QueuesStreamClient struct { 18 | sync.Mutex 19 | clientCtx context.Context 20 | client *GrpcClient 21 | upstream *upstream 22 | downstream *downstream 23 | } 24 | 25 | // NewQueuesStreamClient is a function that creates a new QueuesStreamClient instance. 26 | // It takes a context and an optional list of options. 27 | // It returns a QueuesStreamClient and an error. 28 | // The function creates a new GrpcClient using the provided context and options. 29 | // If the creation of the GrpcClient fails, an error is returned. 30 | // Otherwise, a new QueuesStreamClient is created with the client context and client. 31 | // The QueuesStreamClient is then returned along with a nil error. 32 | func NewQueuesStreamClient(ctx context.Context, op ...Option) (*QueuesStreamClient, error) { 33 | client, err := NewGrpcClient(ctx, op...) 34 | if err != nil { 35 | return nil, err 36 | } 37 | c := &QueuesStreamClient{ 38 | clientCtx: ctx, 39 | client: client, 40 | } 41 | 42 | return c, nil 43 | } 44 | 45 | // Send sends one or more QueueMessages to the QueuesStreamClient. 46 | // It acquires a lock to ensure thread safety. 47 | // If there is no upstream connection, a new upstream connection is created. 48 | // If the upstream connection is not ready, an error is returned. 49 | // If there are no messages to send, an error is returned. 50 | // The messages are converted to a list of pb.QueueMessage structs. 51 | // A new QueuesUpstreamRequest is created with a unique RequestID and the list of messages. 52 | // The request is sent to the upstream server using the send method of the upstream connection. 53 | // The response is received through a select statement, handling both the response and the cancellation of the request. 54 | // If the response indicates an error, an error is returned. 55 | // Otherwise, a new SendResult is created from the response and returned along with no error. 56 | // If the context is canceled before receiving the response, the transaction is canceled and an error is returned. 57 | // If any other error occurs during the execution, it is returned. 58 | func (q *QueuesStreamClient) Send(ctx context.Context, messages ...*QueueMessage) (*SendResult, error) { 59 | q.Lock() 60 | if q.upstream == nil { 61 | q.upstream = newUpstream(ctx, q) 62 | } 63 | q.Unlock() 64 | if !q.upstream.isReady() { 65 | return nil, fmt.Errorf("kubemq grpc client connection lost, can't send messages ") 66 | } 67 | if len(messages) == 0 { 68 | return nil, fmt.Errorf("no messages to send") 69 | } 70 | var list []*pb.QueueMessage 71 | for _, message := range messages { 72 | list = append(list, message.complete(q.client.GlobalClientId()).QueueMessage) 73 | } 74 | req := &pb.QueuesUpstreamRequest{ 75 | RequestID: uuid.New(), 76 | Messages: list, 77 | } 78 | select { 79 | case resp := <-q.upstream.send(req): 80 | if resp.IsError { 81 | return nil, fmt.Errorf(resp.Error) 82 | } 83 | return newSendResult(resp.Results), nil 84 | case <-ctx.Done(): 85 | q.upstream.cancelTransaction(req.RequestID) 86 | return nil, ctx.Err() 87 | } 88 | } 89 | 90 | // Poll retrieves messages from the QueuesStreamClient. 91 | // It checks if the downstream connection is ready and then calls downstream.poll() 92 | func (q *QueuesStreamClient) Poll(ctx context.Context, request *PollRequest) (*PollResponse, error) { 93 | q.Lock() 94 | if q.downstream == nil { 95 | q.downstream = newDownstream(ctx, q) 96 | } 97 | q.Unlock() 98 | if !q.downstream.isReady() { 99 | return nil, fmt.Errorf("kubemq grpc client connection lost, can't poll messages") 100 | } 101 | pollReq, err := q.downstream.poll(ctx, request, q.client.GlobalClientId()) 102 | return pollReq, err 103 | } 104 | 105 | // AckAll sends an acknowledgment for all messages in the specified channel. 106 | // It validates the request, creates a new AckAllQueueMessagesRequest, 107 | // and sends it to the client's AckAllQueueMessages method. If successful, 108 | // it creates a new AckAllResponse with the response data and returns it. 109 | func (q *QueuesStreamClient) AckAll(ctx context.Context, request *AckAllRequest) (*AckAllResponse, error) { 110 | if err := request.validateAndComplete(q.client.GlobalClientId()); err != nil { 111 | return nil, err 112 | } 113 | req := &pb.AckAllQueueMessagesRequest{ 114 | RequestID: request.requestID, 115 | ClientID: request.ClientID, 116 | Channel: request.Channel, 117 | WaitTimeSeconds: request.WaitTimeSeconds, 118 | } 119 | pbResp, err := q.client.AckAllQueueMessages(ctx, req) 120 | if err != nil { 121 | return nil, err 122 | } 123 | resp := &AckAllResponse{ 124 | RequestID: pbResp.RequestID, 125 | AffectedMessages: pbResp.AffectedMessages, 126 | IsError: pbResp.IsError, 127 | Error: pbResp.Error, 128 | } 129 | return resp, nil 130 | } 131 | 132 | // QueuesInfo returns information about queues based on the provided filter. 133 | // It sends a request to the gRPC client to retrieve QueuesInfoResponse. 134 | // The response is then transformed into a QueuesInfo struct and returned. 135 | func (q *QueuesStreamClient) QueuesInfo(ctx context.Context, filter string) (*QueuesInfo, error) { 136 | resp, err := q.client.QueuesInfo(ctx, &pb.QueuesInfoRequest{ 137 | RequestID: uuid.New(), 138 | QueueName: filter, 139 | }) 140 | if err != nil { 141 | return nil, err 142 | } 143 | return fromQueuesInfoPb(resp.Info), nil 144 | } 145 | 146 | // Create sends a create channel request to the Kubemq server. 147 | // It creates a new channel of type "queues" with the specified channel name. 148 | // It returns an error if the request fails or if there is an error creating the channel. 149 | func (q *QueuesStreamClient) Create(ctx context.Context, channel string) error { 150 | clientId := q.client.GlobalClientId() 151 | resp, err := q.client.SendRequest(ctx, &pb.Request{ 152 | RequestID: uuid.New(), 153 | RequestTypeData: 2, 154 | ClientID: clientId, 155 | Channel: "kubemq.cluster.internal.requests", 156 | Metadata: "create-channel", 157 | Body: nil, 158 | ReplyChannel: "", 159 | Timeout: 10000, 160 | CacheKey: "", 161 | CacheTTL: 0, 162 | Span: nil, 163 | Tags: map[string]string{ 164 | "channel_type": "queues", 165 | "channel": channel, 166 | "client_id": clientId, 167 | }}) 168 | 169 | if err != nil { 170 | return fmt.Errorf("error sending create channel request: %s", err.Error()) 171 | } 172 | 173 | if resp.Error != "" { 174 | return fmt.Errorf("error creating channel: %s", resp.Error) 175 | } 176 | 177 | return nil 178 | } 179 | 180 | // Delete deletes a channel in the QueuesStreamClient. It sends a request to the 181 | // server to delete the specified channel. If the request encounters an error 182 | // while sending or if the response contains an error message, an error is returned. 183 | // 184 | // Parameters: 185 | // - ctx: the context.Context for the request 186 | // - channel: the name of the channel to delete 187 | // 188 | // Returns: 189 | // - error: an error if the delete channel request fails, otherwise nil 190 | func (q *QueuesStreamClient) Delete(ctx context.Context, channel string) error { 191 | clientId := q.client.GlobalClientId() 192 | resp, err := q.client.SendRequest(ctx, &pb.Request{ 193 | RequestID: uuid.New(), 194 | RequestTypeData: 2, 195 | ClientID: clientId, 196 | Channel: "kubemq.cluster.internal.requests", 197 | Metadata: "delete-channel", 198 | Body: nil, 199 | ReplyChannel: "", 200 | Timeout: 10000, 201 | CacheKey: "", 202 | CacheTTL: 0, 203 | Span: nil, 204 | Tags: map[string]string{ 205 | "channel_type": "queues", 206 | "channel": channel, 207 | "client_id": clientId, 208 | }}) 209 | 210 | if err != nil { 211 | return fmt.Errorf("error sending delete channel request: %s", err.Error()) 212 | } 213 | 214 | if resp.Error != "" { 215 | return fmt.Errorf("error deleting channel: %s", resp.Error) 216 | } 217 | 218 | return nil 219 | } 220 | 221 | // List returns a list of QueuesChannels based on the given search string. 222 | // It sends a request to the Kubemq server to retrieve the list of channels. 223 | // The search string is used to filter the channels. 224 | // It returns the list of QueuesChannels and any error encountered. 225 | func (q *QueuesStreamClient) List(ctx context.Context, search string) ([]*common.QueuesChannel, error) { 226 | clientId := q.client.GlobalClientId() 227 | resp, err := q.client.SendRequest(ctx, &pb.Request{ 228 | RequestID: uuid.New(), 229 | RequestTypeData: 2, 230 | ClientID: clientId, 231 | Channel: "kubemq.cluster.internal.requests", 232 | Metadata: "list-channels", 233 | Body: nil, 234 | ReplyChannel: "", 235 | Timeout: 10000, 236 | CacheKey: "", 237 | CacheTTL: 0, 238 | Span: nil, 239 | Tags: map[string]string{ 240 | "channel_type": "queues", 241 | "client_id": clientId, 242 | "search": search, 243 | }}) 244 | 245 | if err != nil { 246 | return nil, fmt.Errorf("error sending list channel request: %s", err.Error()) 247 | } 248 | 249 | if resp.Error != "" { 250 | return nil, fmt.Errorf("error list channels: %s", resp.Error) 251 | } 252 | channels, err := kubemq.DecodeQueuesChannelList(resp.Body) 253 | if err != nil { 254 | return nil, err 255 | } 256 | return channels, nil 257 | } 258 | 259 | // Close closes the QueuesStreamClient by closing the upstream and downstream connections 260 | // and then closing the underlying GrpcClient connection. 261 | // It also sleeps for 100 milliseconds before closing the connections. 262 | // Returns an error if any of the close operations encounter an error, or if closing 263 | // the underlying GrpcClient encounters an error. 264 | func (q *QueuesStreamClient) Close() error { 265 | time.Sleep(100 * time.Millisecond) 266 | if q.upstream != nil { 267 | q.upstream.close() 268 | } 269 | if q.downstream != nil { 270 | q.downstream.close() 271 | } 272 | return q.client.Close() 273 | } 274 | -------------------------------------------------------------------------------- /queues_stream/client_test.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/kubemq-io/kubemq-go/pkg/uuid" 8 | "github.com/stretchr/testify/require" 9 | "go.uber.org/atomic" 10 | "sync" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | func createBatchMessages(channel string, amount int) []*QueueMessage { 16 | var list []*QueueMessage 17 | for i := 0; i < amount; i++ { 18 | list = append(list, NewQueueMessage(). 19 | SetId(uuid.New()). 20 | SetChannel(channel). 21 | SetBody([]byte(fmt.Sprintf("message %d", i)))) 22 | } 23 | return list 24 | } 25 | 26 | func TestQueuesClient_Send(t *testing.T) { 27 | ctx, cancel := context.WithCancel(context.Background()) 28 | defer cancel() 29 | queueClient, err := NewQueuesStreamClient(ctx, 30 | WithAddress("localhost", 50000), 31 | WithClientId(uuid.New()), 32 | ) 33 | require.NoError(t, err) 34 | messages := createBatchMessages("queues.send", 10) 35 | result, err := queueClient.Send(ctx, messages...) 36 | require.NoError(t, err) 37 | require.EqualValues(t, 10, len(result.Results)) 38 | for _, messageResult := range result.Results { 39 | require.False(t, messageResult.IsError) 40 | } 41 | } 42 | func TestQueuesClient_Send_WithInvalid_Data(t *testing.T) { 43 | ctx, cancel := context.WithCancel(context.Background()) 44 | defer cancel() 45 | queueClient, err := NewQueuesStreamClient(ctx, 46 | WithAddress("localhost", 50000), 47 | WithClientId(uuid.New()), 48 | ) 49 | require.NoError(t, err) 50 | messages := createBatchMessages("", 10) 51 | result, err := queueClient.Send(ctx, messages...) 52 | require.NoError(t, err) 53 | require.EqualValues(t, 10, len(result.Results)) 54 | for _, messageResult := range result.Results { 55 | require.True(t, messageResult.IsError) 56 | } 57 | } 58 | func TestQueuesClient_Send_Concurrent(t *testing.T) { 59 | ctx, cancel := context.WithCancel(context.Background()) 60 | defer cancel() 61 | queueClient, err := NewQueuesStreamClient(ctx, 62 | WithAddress("localhost", 50000), 63 | WithClientId(uuid.New()), 64 | ) 65 | require.NoError(t, err) 66 | counter := *atomic.NewInt32(0) 67 | wg := sync.WaitGroup{} 68 | wg.Add(20) 69 | 70 | for i := 0; i < 20; i++ { 71 | go func(index int) { 72 | defer wg.Done() 73 | messages := createBatchMessages(fmt.Sprintf("queues.send.%d", index), 20000) 74 | result, err := queueClient.Send(ctx, messages...) 75 | require.NoError(t, err) 76 | require.EqualValues(t, 20000, len(result.Results)) 77 | for _, messageResult := range result.Results { 78 | require.False(t, messageResult.IsError) 79 | } 80 | counter.Add(int32(len(result.Results))) 81 | }(i) 82 | } 83 | wg.Wait() 84 | require.EqualValues(t, int32(400000), counter.Load()) 85 | 86 | } 87 | func TestQueuesClient_Poll_AutoAck(t *testing.T) { 88 | ctx, cancel := context.WithCancel(context.Background()) 89 | defer cancel() 90 | queueClient, err := NewQueuesStreamClient(ctx, 91 | WithAddress("localhost", 50000), 92 | WithClientId(uuid.New()), 93 | ) 94 | require.NoError(t, err) 95 | testChannel := uuid.New() 96 | messagesCount := 10 97 | messages := createBatchMessages(testChannel, messagesCount) 98 | result, err := queueClient.Send(ctx, messages...) 99 | require.NoError(t, err) 100 | require.EqualValues(t, 10, len(result.Results)) 101 | for _, messageResult := range result.Results { 102 | require.False(t, messageResult.IsError) 103 | } 104 | pollRequest := NewPollRequest(). 105 | SetChannel(testChannel). 106 | SetAutoAck(true). 107 | SetMaxItems(messagesCount). 108 | SetWaitTimeout(1000) 109 | response, err := queueClient.Poll(ctx, pollRequest) 110 | require.NoError(t, err) 111 | require.EqualValues(t, messagesCount, len(response.Messages)) 112 | pollRequest2 := NewPollRequest(). 113 | SetChannel(testChannel). 114 | SetAutoAck(true). 115 | SetMaxItems(messagesCount). 116 | SetWaitTimeout(1000) 117 | response2, err := queueClient.Poll(ctx, pollRequest2) 118 | require.NoError(t, err) 119 | require.EqualValues(t, 0, len(response2.Messages)) 120 | } 121 | func TestQueuesClient_Poll_Long_AutoAck_ClientContextCancelled(t *testing.T) { 122 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 123 | defer cancel() 124 | queueClient, err := NewQueuesStreamClient(ctx, 125 | WithAddress("localhost", 50000), 126 | WithClientId(uuid.New()), 127 | ) 128 | require.NoError(t, err) 129 | testChannel := uuid.New() 130 | messagesCount := 10 131 | pollRequest := NewPollRequest(). 132 | SetChannel(testChannel). 133 | SetAutoAck(true). 134 | SetMaxItems(messagesCount). 135 | SetWaitTimeout(10000) 136 | response, err := queueClient.Poll(ctx, pollRequest) 137 | require.Error(t, err) 138 | require.Nil(t, response) 139 | } 140 | 141 | func TestQueuesClient_Poll_ManualAck_AckAll(t *testing.T) { 142 | ctx, cancel := context.WithCancel(context.Background()) 143 | defer cancel() 144 | queueClient, err := NewQueuesStreamClient(ctx, 145 | WithAddress("localhost", 50000), 146 | WithClientId(uuid.New()), 147 | ) 148 | require.NoError(t, err) 149 | testChannel := uuid.New() 150 | messagesCount := 10 151 | messages := createBatchMessages(testChannel, messagesCount) 152 | result, err := queueClient.Send(ctx, messages...) 153 | require.NoError(t, err) 154 | require.EqualValues(t, 10, len(result.Results)) 155 | for _, messageResult := range result.Results { 156 | require.False(t, messageResult.IsError) 157 | } 158 | pollRequest := NewPollRequest(). 159 | SetChannel(testChannel). 160 | SetAutoAck(false). 161 | SetMaxItems(messagesCount). 162 | SetWaitTimeout(1000) 163 | 164 | response, err := queueClient.Poll(ctx, pollRequest) 165 | require.NoError(t, err) 166 | require.EqualValues(t, messagesCount, len(response.Messages)) 167 | err = response.AckAll() 168 | require.NoError(t, err) 169 | time.Sleep(100 * time.Millisecond) 170 | err = response.AckAll() 171 | 172 | pollRequest2 := NewPollRequest(). 173 | SetChannel(testChannel). 174 | SetAutoAck(true). 175 | SetMaxItems(messagesCount). 176 | SetWaitTimeout(1000) 177 | response2, err := queueClient.Poll(ctx, pollRequest2) 178 | require.NoError(t, err) 179 | require.EqualValues(t, 0, len(response2.Messages)) 180 | } 181 | func TestQueuesClient_Poll_ManualAck_NAckAll(t *testing.T) { 182 | ctx, cancel := context.WithCancel(context.Background()) 183 | defer cancel() 184 | queueClient, err := NewQueuesStreamClient(ctx, 185 | WithAddress("localhost", 50000), 186 | WithClientId(uuid.New()), 187 | ) 188 | require.NoError(t, err) 189 | testChannel := uuid.New() 190 | messagesCount := 10 191 | messages := createBatchMessages(testChannel, messagesCount) 192 | result, err := queueClient.Send(ctx, messages...) 193 | require.NoError(t, err) 194 | require.EqualValues(t, 10, len(result.Results)) 195 | for _, messageResult := range result.Results { 196 | require.False(t, messageResult.IsError) 197 | } 198 | 199 | pollRequest := NewPollRequest(). 200 | SetChannel(testChannel). 201 | SetAutoAck(false). 202 | SetMaxItems(messagesCount). 203 | SetWaitTimeout(1000) 204 | 205 | response, err := queueClient.Poll(ctx, pollRequest) 206 | require.NoError(t, err) 207 | require.EqualValues(t, messagesCount, len(response.Messages)) 208 | 209 | err = response.NAckAll() 210 | require.NoError(t, err) 211 | time.Sleep(100 * time.Millisecond) 212 | require.NoError(t, response.NAckAll()) 213 | 214 | pollRequest2 := NewPollRequest(). 215 | SetChannel(testChannel). 216 | SetAutoAck(true). 217 | SetMaxItems(messagesCount). 218 | SetWaitTimeout(1000) 219 | response2, err := queueClient.Poll(ctx, pollRequest2) 220 | require.NoError(t, err) 221 | require.EqualValues(t, messagesCount, len(response2.Messages)) 222 | } 223 | func TestQueuesClient_Poll_ManualAck_ReQueueAll(t *testing.T) { 224 | ctx, cancel := context.WithCancel(context.Background()) 225 | defer cancel() 226 | queueClient, err := NewQueuesStreamClient(ctx, 227 | WithAddress("localhost", 50000), 228 | WithClientId(uuid.New()), 229 | ) 230 | require.NoError(t, err) 231 | testChannel := uuid.New() 232 | messagesCount := 10 233 | messages := createBatchMessages(testChannel, messagesCount) 234 | result, err := queueClient.Send(ctx, messages...) 235 | require.NoError(t, err) 236 | require.EqualValues(t, 10, len(result.Results)) 237 | for _, messageResult := range result.Results { 238 | require.False(t, messageResult.IsError) 239 | } 240 | pollRequest := NewPollRequest(). 241 | SetChannel(testChannel). 242 | SetAutoAck(false). 243 | SetMaxItems(messagesCount). 244 | SetWaitTimeout(1000) 245 | 246 | response, err := queueClient.Poll(ctx, pollRequest) 247 | require.NoError(t, err) 248 | require.EqualValues(t, messagesCount, len(response.Messages)) 249 | reQueueChannel := uuid.New() 250 | err = response.ReQueueAll(reQueueChannel) 251 | require.NoError(t, err) 252 | time.Sleep(100 * time.Millisecond) 253 | require.NoError(t, response.ReQueueAll(reQueueChannel)) 254 | 255 | pollRequest2 := NewPollRequest(). 256 | SetChannel(testChannel). 257 | SetAutoAck(true). 258 | SetMaxItems(messagesCount). 259 | SetWaitTimeout(1000) 260 | response2, err := queueClient.Poll(ctx, pollRequest2) 261 | require.NoError(t, err) 262 | require.EqualValues(t, 0, len(response2.Messages)) 263 | pollRequest3 := NewPollRequest(). 264 | SetChannel(reQueueChannel). 265 | SetAutoAck(true). 266 | SetMaxItems(messagesCount). 267 | SetWaitTimeout(1000) 268 | response3, err := queueClient.Poll(ctx, pollRequest3) 269 | require.NoError(t, err) 270 | require.EqualValues(t, messagesCount, len(response3.Messages)) 271 | } 272 | func TestQueuesClient_Poll_ManualAck_AckRange(t *testing.T) { 273 | ctx, cancel := context.WithCancel(context.Background()) 274 | defer cancel() 275 | queueClient, err := NewQueuesStreamClient(ctx, 276 | WithAddress("localhost", 50000), 277 | WithClientId(uuid.New()), 278 | ) 279 | require.NoError(t, err) 280 | testChannel := uuid.New() 281 | messagesCount := 1000 282 | messages := createBatchMessages(testChannel, messagesCount) 283 | result, err := queueClient.Send(ctx, messages...) 284 | require.NoError(t, err) 285 | require.EqualValues(t, messagesCount, len(result.Results)) 286 | for _, messageResult := range result.Results { 287 | require.False(t, messageResult.IsError) 288 | } 289 | pollRequest := NewPollRequest(). 290 | SetChannel(testChannel). 291 | SetAutoAck(false). 292 | SetMaxItems(messagesCount). 293 | SetWaitTimeout(1000) 294 | response, err := queueClient.Poll(ctx, pollRequest) 295 | require.NoError(t, err) 296 | require.EqualValues(t, messagesCount, len(response.Messages)) 297 | for i, msg := range response.Messages { 298 | err := msg.Ack() 299 | require.NoErrorf(t, err, "msg id %d", i) 300 | } 301 | pollRequest2 := NewPollRequest(). 302 | SetChannel(testChannel). 303 | SetAutoAck(true). 304 | SetMaxItems(messagesCount). 305 | SetWaitTimeout(1000) 306 | response2, err := queueClient.Poll(ctx, pollRequest2) 307 | require.NoError(t, err) 308 | require.EqualValues(t, 0, len(response2.Messages)) 309 | } 310 | func TestQueuesClient_Poll_ManualAck_NAckRange(t *testing.T) { 311 | ctx, cancel := context.WithCancel(context.Background()) 312 | defer cancel() 313 | queueClient, err := NewQueuesStreamClient(ctx, 314 | WithAddress("localhost", 50000), 315 | WithClientId(uuid.New()), 316 | ) 317 | require.NoError(t, err) 318 | testChannel := uuid.New() 319 | messagesCount := 1000 320 | messages := createBatchMessages(testChannel, messagesCount) 321 | result, err := queueClient.Send(ctx, messages...) 322 | require.NoError(t, err) 323 | require.EqualValues(t, messagesCount, len(result.Results)) 324 | for _, messageResult := range result.Results { 325 | require.False(t, messageResult.IsError) 326 | } 327 | pollRequest := NewPollRequest(). 328 | SetChannel(testChannel). 329 | SetAutoAck(false). 330 | SetMaxItems(messagesCount). 331 | SetWaitTimeout(1000) 332 | response, err := queueClient.Poll(ctx, pollRequest) 333 | require.NoError(t, err) 334 | require.EqualValues(t, messagesCount, len(response.Messages)) 335 | for i, msg := range response.Messages { 336 | err := msg.NAck() 337 | require.NoErrorf(t, err, "msg id %d", i) 338 | } 339 | pollRequest2 := NewPollRequest(). 340 | SetChannel(testChannel). 341 | SetAutoAck(true). 342 | SetMaxItems(messagesCount). 343 | SetWaitTimeout(1000) 344 | response2, err := queueClient.Poll(ctx, pollRequest2) 345 | require.NoError(t, err) 346 | require.EqualValues(t, messagesCount, len(response2.Messages)) 347 | } 348 | func TestQueuesClient_Poll_ManualAck_ReQueueRange(t *testing.T) { 349 | ctx, cancel := context.WithCancel(context.Background()) 350 | defer cancel() 351 | queueClient, err := NewQueuesStreamClient(ctx, 352 | WithAddress("localhost", 50000), 353 | WithClientId(uuid.New()), 354 | ) 355 | require.NoError(t, err) 356 | testChannel := uuid.New() 357 | messagesCount := 1000 358 | messages := createBatchMessages(testChannel, messagesCount) 359 | result, err := queueClient.Send(ctx, messages...) 360 | require.NoError(t, err) 361 | require.EqualValues(t, messagesCount, len(result.Results)) 362 | for _, messageResult := range result.Results { 363 | require.False(t, messageResult.IsError) 364 | } 365 | pollRequest := NewPollRequest(). 366 | SetChannel(testChannel). 367 | SetAutoAck(false). 368 | SetMaxItems(messagesCount). 369 | SetWaitTimeout(1000) 370 | response, err := queueClient.Poll(ctx, pollRequest) 371 | require.NoError(t, err) 372 | require.EqualValues(t, messagesCount, len(response.Messages)) 373 | reQueueChannel := uuid.New() 374 | for i, msg := range response.Messages { 375 | err := msg.ReQueue(reQueueChannel) 376 | require.NoErrorf(t, err, "msg id %d", i) 377 | } 378 | pollRequest2 := NewPollRequest(). 379 | SetChannel(reQueueChannel). 380 | SetAutoAck(true). 381 | SetMaxItems(messagesCount). 382 | SetWaitTimeout(1000) 383 | response2, err := queueClient.Poll(ctx, pollRequest2) 384 | require.NoError(t, err) 385 | require.EqualValues(t, messagesCount, len(response2.Messages)) 386 | pollRequest3 := NewPollRequest(). 387 | SetChannel(testChannel). 388 | SetAutoAck(true). 389 | SetMaxItems(messagesCount). 390 | SetWaitTimeout(1000) 391 | response3, err := queueClient.Poll(ctx, pollRequest3) 392 | require.NoError(t, err) 393 | require.EqualValues(t, 0, len(response3.Messages)) 394 | } 395 | func TestQueuesClient_Poll_ManualAck_Close(t *testing.T) { 396 | ctx, cancel := context.WithCancel(context.Background()) 397 | defer cancel() 398 | queueClient, err := NewQueuesStreamClient(ctx, 399 | WithAddress("localhost", 50000), 400 | WithClientId(uuid.New()), 401 | ) 402 | require.NoError(t, err) 403 | testChannel := uuid.New() 404 | messagesCount := 1000 405 | messages := createBatchMessages(testChannel, messagesCount) 406 | result, err := queueClient.Send(ctx, messages...) 407 | require.NoError(t, err) 408 | require.EqualValues(t, messagesCount, len(result.Results)) 409 | for _, messageResult := range result.Results { 410 | require.False(t, messageResult.IsError) 411 | } 412 | pollRequest := NewPollRequest(). 413 | SetChannel(testChannel). 414 | SetAutoAck(false). 415 | SetMaxItems(messagesCount). 416 | SetWaitTimeout(1000) 417 | response, err := queueClient.Poll(ctx, pollRequest) 418 | require.NoError(t, err) 419 | require.EqualValues(t, messagesCount, len(response.Messages)) 420 | err = response.Close() 421 | require.NoError(t, err) 422 | pollRequest2 := NewPollRequest(). 423 | SetChannel(testChannel). 424 | SetAutoAck(true). 425 | SetMaxItems(messagesCount). 426 | SetWaitTimeout(1000) 427 | response2, err := queueClient.Poll(ctx, pollRequest2) 428 | require.NoError(t, err) 429 | require.EqualValues(t, messagesCount, len(response2.Messages)) 430 | } 431 | func TestQueuesClient_Poll_ManualAck_ContextCancelled(t *testing.T) { 432 | ctx, cancel := context.WithCancel(context.Background()) 433 | 434 | queueClient, err := NewQueuesStreamClient(ctx, 435 | WithAddress("localhost", 50000), 436 | WithClientId(uuid.New()), 437 | ) 438 | require.NoError(t, err) 439 | testChannel := uuid.New() 440 | messagesCount := 1000 441 | messages := createBatchMessages(testChannel, messagesCount) 442 | result, err := queueClient.Send(ctx, messages...) 443 | require.NoError(t, err) 444 | require.EqualValues(t, messagesCount, len(result.Results)) 445 | for _, messageResult := range result.Results { 446 | require.False(t, messageResult.IsError) 447 | } 448 | pollRequest := NewPollRequest(). 449 | SetChannel(testChannel). 450 | SetAutoAck(false). 451 | SetMaxItems(messagesCount). 452 | SetWaitTimeout(1000) 453 | response, err := queueClient.Poll(ctx, pollRequest) 454 | require.NoError(t, err) 455 | require.EqualValues(t, messagesCount, len(response.Messages)) 456 | time.Sleep(time.Second) 457 | cancel() 458 | 459 | pollRequest2 := NewPollRequest(). 460 | SetChannel(testChannel). 461 | SetAutoAck(true). 462 | SetMaxItems(messagesCount). 463 | SetWaitTimeout(1000) 464 | response2, err := queueClient.Poll(context.Background(), pollRequest2) 465 | require.Error(t, err) 466 | require.Nil(t, response2) 467 | newCtx, newCancel := context.WithCancel(context.Background()) 468 | defer newCancel() 469 | queueClient, err = NewQueuesStreamClient(newCtx, 470 | WithAddress("localhost", 50000), 471 | WithClientId(uuid.New()), 472 | ) 473 | require.NoError(t, err) 474 | pollRequest3 := NewPollRequest(). 475 | SetChannel(testChannel). 476 | SetAutoAck(true). 477 | SetMaxItems(messagesCount). 478 | SetWaitTimeout(1000) 479 | response3, err := queueClient.Poll(newCtx, pollRequest3) 480 | require.NoError(t, err) 481 | require.EqualValues(t, messagesCount, len(response3.Messages)) 482 | } 483 | func TestQueuesClient_Poll_ManualAck_CloseClient(t *testing.T) { 484 | ctx, cancel := context.WithCancel(context.Background()) 485 | defer cancel() 486 | queueClient, err := NewQueuesStreamClient(ctx, 487 | WithAddress("localhost", 50000), 488 | WithClientId(uuid.New()), 489 | ) 490 | require.NoError(t, err) 491 | testChannel := uuid.New() 492 | messagesCount := 1000 493 | messages := createBatchMessages(testChannel, messagesCount) 494 | result, err := queueClient.Send(ctx, messages...) 495 | require.NoError(t, err) 496 | require.EqualValues(t, messagesCount, len(result.Results)) 497 | for _, messageResult := range result.Results { 498 | require.False(t, messageResult.IsError) 499 | } 500 | pollRequest := NewPollRequest(). 501 | SetChannel(testChannel). 502 | SetAutoAck(false). 503 | SetMaxItems(messagesCount). 504 | SetWaitTimeout(1000) 505 | response, err := queueClient.Poll(ctx, pollRequest) 506 | require.NoError(t, err) 507 | require.EqualValues(t, messagesCount, len(response.Messages)) 508 | time.Sleep(time.Second) 509 | err = queueClient.Close() 510 | require.NoError(t, err) 511 | queueClient, err = NewQueuesStreamClient(ctx, 512 | WithAddress("localhost", 50000), 513 | WithClientId(uuid.New()), 514 | ) 515 | require.NoError(t, err) 516 | pollRequest3 := NewPollRequest(). 517 | SetChannel(testChannel). 518 | SetAutoAck(true). 519 | SetMaxItems(messagesCount). 520 | SetWaitTimeout(1000) 521 | response3, err := queueClient.Poll(ctx, pollRequest3) 522 | require.NoError(t, err) 523 | require.EqualValues(t, messagesCount, len(response3.Messages)) 524 | } 525 | func TestQueuesClient_Poll_InvalidRequest(t *testing.T) { 526 | ctx, cancel := context.WithCancel(context.Background()) 527 | defer cancel() 528 | queueClient, err := NewQueuesStreamClient(ctx, 529 | WithAddress("localhost", 50000), 530 | WithClientId(uuid.New()), 531 | ) 532 | require.NoError(t, err) 533 | pollRequest := NewPollRequest(). 534 | SetChannel(""). 535 | SetAutoAck(false). 536 | SetMaxItems(0). 537 | SetWaitTimeout(1000) 538 | response, err := queueClient.Poll(ctx, pollRequest) 539 | require.Error(t, err) 540 | require.Nil(t, response) 541 | 542 | } 543 | func TestQueuesClient_Poll_InvalidRequestByServer(t *testing.T) { 544 | ctx, cancel := context.WithCancel(context.Background()) 545 | defer cancel() 546 | queueClient, err := NewQueuesStreamClient(ctx, 547 | WithAddress("localhost", 50000), 548 | WithClientId(uuid.New()), 549 | ) 550 | require.NoError(t, err) 551 | pollRequest := NewPollRequest(). 552 | SetChannel(">"). 553 | SetAutoAck(false). 554 | SetMaxItems(0). 555 | SetWaitTimeout(1000) 556 | response, err := queueClient.Poll(ctx, pollRequest) 557 | require.Error(t, err) 558 | require.Nil(t, response) 559 | 560 | } 561 | -------------------------------------------------------------------------------- /queues_stream/downstream.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | pb "github.com/kubemq-io/protobuf/go" 7 | "go.uber.org/atomic" 8 | "io" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type downstream struct { 14 | sync.Mutex 15 | downstreamCtx context.Context 16 | downstreamCancel context.CancelFunc 17 | pendingTransactions map[string]*responseHandler 18 | activeTransactions map[string]*responseHandler 19 | requestCh chan *pb.QueuesDownstreamRequest 20 | responseCh chan *pb.QueuesDownstreamResponse 21 | errCh chan error 22 | doneCh chan bool 23 | grpcClient pb.KubemqClient 24 | streamClient *QueuesStreamClient 25 | isClosed bool 26 | connectionState *atomic.Bool 27 | } 28 | 29 | func newDownstream(ctx context.Context, streamClient *QueuesStreamClient) *downstream { 30 | 31 | d := &downstream{ 32 | Mutex: sync.Mutex{}, 33 | downstreamCtx: nil, 34 | downstreamCancel: nil, 35 | pendingTransactions: map[string]*responseHandler{}, 36 | activeTransactions: map[string]*responseHandler{}, 37 | requestCh: make(chan *pb.QueuesDownstreamRequest, 10), 38 | responseCh: make(chan *pb.QueuesDownstreamResponse, 10), 39 | errCh: make(chan error, 10), 40 | doneCh: make(chan bool, 1), 41 | grpcClient: streamClient.client.KubemqClient, 42 | streamClient: streamClient, 43 | isClosed: false, 44 | connectionState: atomic.NewBool(false), 45 | } 46 | d.downstreamCtx, d.downstreamCancel = context.WithCancel(ctx) 47 | go d.run() 48 | time.Sleep(time.Second) 49 | return d 50 | } 51 | 52 | func (d *downstream) close() { 53 | d.setIsClose(true) 54 | d.downstreamCancel() 55 | } 56 | func (d *downstream) setIsClose(value bool) { 57 | d.Lock() 58 | defer d.Unlock() 59 | d.isClosed = value 60 | } 61 | func (d *downstream) getIsClose() bool { 62 | d.Lock() 63 | defer d.Unlock() 64 | return d.isClosed 65 | } 66 | func (d *downstream) sendOnConnectionState(msg string) { 67 | if d.streamClient.client.opts.connectionNotificationFunc != nil { 68 | go func() { 69 | d.streamClient.client.opts.connectionNotificationFunc(msg) 70 | }() 71 | } 72 | } 73 | func (d *downstream) createPendingTransaction(request *pb.QueuesDownstreamRequest, visibilitySeconds int) *responseHandler { 74 | d.Lock() 75 | defer d.Unlock() 76 | handler := newResponseHandler(). 77 | setRequestId(request.RequestID). 78 | setRequestChanel(request.Channel). 79 | setRequestClientId(request.ClientID). 80 | setRequestCh(d.requestCh). 81 | setVisibilitySeconds(visibilitySeconds). 82 | setIsAutoAck(request.AutoAck) 83 | 84 | d.pendingTransactions[request.RequestID] = handler 85 | return handler 86 | } 87 | func (d *downstream) movePendingToActiveTransaction(requestId, transactionId string) (*responseHandler, bool) { 88 | d.Lock() 89 | defer d.Unlock() 90 | handler, ok := d.pendingTransactions[requestId] 91 | if ok { 92 | handler.transactionId = transactionId 93 | d.activeTransactions[transactionId] = handler 94 | delete(d.pendingTransactions, requestId) 95 | return handler, true 96 | } else { 97 | return nil, false 98 | } 99 | } 100 | func (d *downstream) deletePendingTransaction(requestId string) { 101 | d.Lock() 102 | defer d.Unlock() 103 | delete(d.pendingTransactions, requestId) 104 | //log.Println("pending transaction deleted", requestId) 105 | } 106 | 107 | func (d *downstream) getActiveTransaction(id string) (*responseHandler, bool) { 108 | d.Lock() 109 | defer d.Unlock() 110 | handler, ok := d.activeTransactions[id] 111 | return handler, ok 112 | } 113 | 114 | func (d *downstream) deleteActiveTransaction(id string) { 115 | d.Lock() 116 | defer d.Unlock() 117 | delete(d.activeTransactions, id) 118 | //log.Println("active transaction deleted", id) 119 | } 120 | func (d *downstream) connectStream(ctx context.Context) { 121 | defer func() { 122 | d.doneCh <- true 123 | d.connectionState.Store(false) 124 | d.sendOnConnectionState(fmt.Sprintf("grpc queue client downstream disconnected")) 125 | }() 126 | stream, err := d.grpcClient.QueuesDownstream(ctx) 127 | if err != nil { 128 | d.errCh <- err 129 | d.sendOnConnectionState(fmt.Sprintf("grpc queue client downstream connection error, %s", err.Error())) 130 | return 131 | } 132 | d.connectionState.Store(true) 133 | d.sendOnConnectionState("grpc queue client downstream connected") 134 | go func() { 135 | for { 136 | res, err := stream.Recv() 137 | if err != nil { 138 | if err == io.EOF { 139 | return 140 | } 141 | d.errCh <- err 142 | d.sendOnConnectionState(fmt.Sprintf("grpc queue client downstream receive error, %s", err.Error())) 143 | return 144 | } 145 | select { 146 | case d.responseCh <- res: 147 | case <-stream.Context().Done(): 148 | return 149 | case <-ctx.Done(): 150 | return 151 | } 152 | } 153 | }() 154 | 155 | for { 156 | select { 157 | case req := <-d.requestCh: 158 | err := stream.Send(req) 159 | if err != nil { 160 | if err == io.EOF { 161 | return 162 | } 163 | d.errCh <- err 164 | d.sendOnConnectionState(fmt.Sprintf("grpc queue client downstream send error, %s", err.Error())) 165 | return 166 | } 167 | case <-stream.Context().Done(): 168 | return 169 | case <-ctx.Done(): 170 | return 171 | } 172 | } 173 | 174 | } 175 | func (d *downstream) clearPendingTransactions(err error) { 176 | d.Lock() 177 | d.Unlock() 178 | for id, handler := range d.pendingTransactions { 179 | select { 180 | case handler.errCh <- err: 181 | default: 182 | 183 | } 184 | handler.sendError(err) 185 | handler.sendComplete() 186 | delete(d.pendingTransactions, id) 187 | } 188 | } 189 | func (d *downstream) clearActiveTransactions(err error) { 190 | d.Lock() 191 | d.Unlock() 192 | for id, handler := range d.activeTransactions { 193 | select { 194 | case handler.errCh <- err: 195 | default: 196 | } 197 | handler.sendError(err) 198 | handler.sendComplete() 199 | delete(d.activeTransactions, id) 200 | } 201 | } 202 | func (d *downstream) run() { 203 | for { 204 | if !d.getIsClose() { 205 | go d.connectStream(d.downstreamCtx) 206 | } else { 207 | return 208 | } 209 | for { 210 | select { 211 | case resp := <-d.responseCh: 212 | if resp.RequestTypeData == pb.QueuesDownstreamRequestType_Get { 213 | handler, ok := d.movePendingToActiveTransaction(resp.RefRequestId, resp.TransactionId) 214 | if ok { 215 | handler.responseCh <- resp 216 | } 217 | } else { 218 | handler, ok := d.getActiveTransaction(resp.TransactionId) 219 | if ok { 220 | if resp.TransactionComplete { 221 | handler.sendComplete() 222 | d.deleteActiveTransaction(resp.TransactionId) 223 | continue 224 | } 225 | if resp.IsError { 226 | handler.sendError(fmt.Errorf(resp.Error)) 227 | continue 228 | } 229 | } 230 | } 231 | case err := <-d.errCh: 232 | d.clearPendingTransactions(err) 233 | d.clearActiveTransactions(err) 234 | case <-d.doneCh: 235 | goto reconnect 236 | case <-d.downstreamCtx.Done(): 237 | d.clearPendingTransactions(d.downstreamCtx.Err()) 238 | d.clearActiveTransactions(d.downstreamCtx.Err()) 239 | return 240 | } 241 | } 242 | reconnect: 243 | time.Sleep(time.Second) 244 | } 245 | } 246 | func (d *downstream) isReady() bool { 247 | return d.connectionState.Load() 248 | } 249 | func (d *downstream) poll(ctx context.Context, request *PollRequest, clientId string) (*PollResponse, error) { 250 | pbReq, err := request.validateAndComplete(clientId) 251 | if err != nil { 252 | return nil, err 253 | } 254 | respHandler := d.createPendingTransaction(pbReq, request.VisibilitySeconds). 255 | setOnErrorFunc(request.OnErrorFunc). 256 | setOnCompleteFunc(request.OnComplete) 257 | select { 258 | case d.requestCh <- pbReq: 259 | case <-time.After(time.Second): 260 | return nil, fmt.Errorf("sending poll request timout error") 261 | } 262 | 263 | waitFirstResponse := request.WaitTimeout 264 | if waitFirstResponse == 0 { 265 | waitFirstResponse = 60000 266 | } else { 267 | waitFirstResponse = request.WaitTimeout + 10000 268 | } 269 | waitResponseCtx, waitResponseCancel := context.WithTimeout(ctx, time.Duration(waitFirstResponse)*time.Millisecond) 270 | defer waitResponseCancel() 271 | select { 272 | case resp := <-respHandler.responseCh: 273 | if resp.IsError { 274 | return nil, fmt.Errorf(resp.Error) 275 | } 276 | pollResponse := newPollResponse(resp.Messages, respHandler) 277 | if len(pollResponse.Messages) > 0 && !pbReq.AutoAck { 278 | respHandler.start(ctx) 279 | } else { 280 | respHandler.sendComplete() 281 | d.deleteActiveTransaction(resp.TransactionId) 282 | } 283 | return pollResponse, nil 284 | case connectionErr := <-respHandler.errCh: 285 | return nil, fmt.Errorf("grpcClient connection error, %s", connectionErr.Error()) 286 | case <-waitResponseCtx.Done(): 287 | d.deletePendingTransaction(pbReq.RequestID) 288 | return nil, fmt.Errorf("timeout waiting response for poll messages request") 289 | case <-d.downstreamCtx.Done(): 290 | respHandler.sendError(d.downstreamCtx.Err()) 291 | respHandler.sendComplete() 292 | d.deletePendingTransaction(pbReq.RequestID) 293 | return nil, d.downstreamCtx.Err() 294 | case <-ctx.Done(): 295 | respHandler.sendError(ctx.Err()) 296 | respHandler.sendComplete() 297 | d.deletePendingTransaction(pbReq.RequestID) 298 | return nil, ctx.Err() 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /queues_stream/grpc.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "context" 5 | "crypto/x509" 6 | 7 | "fmt" 8 | 9 | pb "github.com/kubemq-io/protobuf/go" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/credentials" 12 | "google.golang.org/grpc/metadata" 13 | ) 14 | 15 | const ( 16 | defaultMaxSendSize = 1024 * 1024 * 100 //100MB 17 | defaultMaxRcvSize = 1024 * 1024 * 100 //100MB 18 | 19 | ) 20 | 21 | type GrpcClient struct { 22 | opts *Options 23 | conn *grpc.ClientConn 24 | pb.KubemqClient 25 | } 26 | 27 | func NewGrpcClient(ctx context.Context, op ...Option) (*GrpcClient, error) { 28 | opts := GetDefaultOptions() 29 | for _, o := range op { 30 | o.apply(opts) 31 | } 32 | if err := opts.Validate(); err != nil { 33 | return nil, err 34 | } 35 | connOptions := []grpc.DialOption{grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaultMaxRcvSize), grpc.MaxCallSendMsgSize(defaultMaxSendSize))} 36 | if opts.isSecured { 37 | if opts.certFile != "" { 38 | creds, err := credentials.NewClientTLSFromFile(opts.certFile, opts.serverOverrideDomain) 39 | if err != nil { 40 | return nil, fmt.Errorf("could not load tls cert: %s", err) 41 | } 42 | connOptions = append(connOptions, grpc.WithTransportCredentials(creds)) 43 | } else if opts.certData != "" { 44 | certPool := x509.NewCertPool() 45 | if !certPool.AppendCertsFromPEM([]byte(opts.certData)) { 46 | return nil, fmt.Errorf("credentials: failed to append certificates to pool") 47 | } 48 | creds := credentials.NewClientTLSFromCert(certPool, opts.serverOverrideDomain) 49 | 50 | connOptions = append(connOptions, grpc.WithTransportCredentials(creds)) 51 | 52 | } else { 53 | return nil, fmt.Errorf("no valid tls security provided") 54 | } 55 | 56 | } else { 57 | connOptions = append(connOptions, grpc.WithInsecure()) 58 | } 59 | address := fmt.Sprintf("%s:%d", opts.host, opts.port) 60 | g := &GrpcClient{ 61 | opts: opts, 62 | conn: nil, 63 | KubemqClient: nil, 64 | } 65 | connOptions = append(connOptions, grpc.WithUnaryInterceptor(g.setUnaryInterceptor()), grpc.WithStreamInterceptor(g.setStreamInterceptor())) 66 | 67 | var err error 68 | g.conn, err = grpc.DialContext(ctx, address, connOptions...) 69 | if err != nil { 70 | return nil, err 71 | } 72 | go func() { 73 | 74 | <-ctx.Done() 75 | if g.conn != nil { 76 | _ = g.conn.Close() 77 | } 78 | 79 | }() 80 | g.KubemqClient = pb.NewKubemqClient(g.conn) 81 | 82 | if g.opts.checkConnection { 83 | _, err := g.Ping(ctx) 84 | if err != nil { 85 | _ = g.Close() 86 | return nil, err 87 | } 88 | return g, nil 89 | } else { 90 | return g, nil 91 | } 92 | 93 | } 94 | func (g *GrpcClient) GlobalClientId() string { 95 | return g.opts.clientId 96 | } 97 | func (g *GrpcClient) setUnaryInterceptor() grpc.UnaryClientInterceptor { 98 | return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 99 | if g.opts.authToken != "" { 100 | ctx = metadata.AppendToOutgoingContext(ctx, kubeMQAuthTokenHeader, g.opts.authToken) 101 | } 102 | return invoker(ctx, method, req, reply, cc, opts...) 103 | } 104 | } 105 | 106 | func (g *GrpcClient) setStreamInterceptor() grpc.StreamClientInterceptor { 107 | return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 108 | if g.opts.authToken != "" { 109 | ctx = metadata.AppendToOutgoingContext(ctx, kubeMQAuthTokenHeader, g.opts.authToken) 110 | } 111 | return streamer(ctx, desc, cc, method, opts...) 112 | } 113 | } 114 | func (g *GrpcClient) Ping(ctx context.Context) (*pb.PingResult, error) { 115 | return g.KubemqClient.Ping(ctx, &pb.Empty{}) 116 | } 117 | 118 | func (g *GrpcClient) Close() error { 119 | err := g.conn.Close() 120 | if err != nil { 121 | return err 122 | } 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /queues_stream/message.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "fmt" 5 | pb "github.com/kubemq-io/protobuf/go" 6 | "go.uber.org/atomic" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type QueueMessage struct { 12 | *pb.QueueMessage 13 | *responseHandler 14 | visibilityDuration time.Duration 15 | visibilityTimer *time.Timer 16 | isCompleted *atomic.Bool 17 | completeReason string 18 | mu sync.Mutex 19 | } 20 | 21 | func (qm *QueueMessage) complete(clientId string) *QueueMessage { 22 | if qm.ClientID == "" { 23 | qm.ClientID = clientId 24 | } 25 | return qm 26 | } 27 | 28 | func (qm *QueueMessage) setResponseHandler(responseHandler *responseHandler) *QueueMessage { 29 | qm.responseHandler = responseHandler 30 | qm.visibilityDuration = time.Duration(responseHandler.visibilitySeconds) * time.Second 31 | qm.isCompleted.Store(qm.isAutoAck) 32 | if qm.isCompleted.Load() { 33 | qm.completeReason = "auto ack" 34 | } 35 | return qm 36 | } 37 | 38 | func NewQueueMessage() *QueueMessage { 39 | return &QueueMessage{ 40 | QueueMessage: &pb.QueueMessage{ 41 | MessageID: "", 42 | ClientID: "", 43 | Channel: "", 44 | Metadata: "", 45 | Body: nil, 46 | Tags: map[string]string{}, 47 | Attributes: nil, 48 | Policy: &pb.QueueMessagePolicy{}, 49 | }, 50 | } 51 | } 52 | func newQueueMessageFrom(msg *pb.QueueMessage) *QueueMessage { 53 | return &QueueMessage{ 54 | QueueMessage: msg, 55 | isCompleted: atomic.NewBool(false), 56 | } 57 | } 58 | 59 | // SetId - set queue message id, otherwise new random uuid will be set 60 | func (qm *QueueMessage) SetId(id string) *QueueMessage { 61 | qm.MessageID = id 62 | return qm 63 | 64 | } 65 | 66 | // SetClientId - set queue message ClientId - mandatory if default grpcClient was not set 67 | func (qm *QueueMessage) SetClientId(clientId string) *QueueMessage { 68 | qm.ClientID = clientId 69 | return qm 70 | } 71 | 72 | // SetChannel - set queue message Channel - mandatory if default Channel was not set 73 | func (qm *QueueMessage) SetChannel(channel string) *QueueMessage { 74 | qm.Channel = channel 75 | return qm 76 | } 77 | 78 | // SetMetadata - set queue message metadata - mandatory if body field is empty 79 | func (qm *QueueMessage) SetMetadata(metadata string) *QueueMessage { 80 | qm.Metadata = metadata 81 | return qm 82 | } 83 | 84 | // SetBody - set queue message body - mandatory if metadata field is empty 85 | func (qm *QueueMessage) SetBody(body []byte) *QueueMessage { 86 | qm.Body = body 87 | return qm 88 | } 89 | 90 | // SetTags - set key value tags to queue message 91 | func (qm *QueueMessage) SetTags(tags map[string]string) *QueueMessage { 92 | qm.Tags = map[string]string{} 93 | for key, value := range tags { 94 | qm.Tags[key] = value 95 | } 96 | return qm 97 | } 98 | 99 | // AddTag - add key value tags to query message 100 | func (qm *QueueMessage) AddTag(key, value string) *QueueMessage { 101 | if qm.Tags == nil { 102 | qm.Tags = map[string]string{} 103 | } 104 | qm.Tags[key] = value 105 | return qm 106 | } 107 | 108 | // SetPolicyExpirationSeconds - set queue message expiration seconds, 0 never expires 109 | func (qm *QueueMessage) SetPolicyExpirationSeconds(sec int) *QueueMessage { 110 | if qm.Policy == nil { 111 | qm.Policy = &pb.QueueMessagePolicy{} 112 | } 113 | qm.Policy.ExpirationSeconds = int32(sec) 114 | return qm 115 | } 116 | 117 | // SetPolicyDelaySeconds - set queue message delivery delay in seconds, 0 , immediate delivery 118 | func (qm *QueueMessage) SetPolicyDelaySeconds(sec int) *QueueMessage { 119 | if qm.Policy == nil { 120 | qm.Policy = &pb.QueueMessagePolicy{} 121 | } 122 | qm.Policy.DelaySeconds = int32(sec) 123 | return qm 124 | } 125 | 126 | // SetPolicyMaxReceiveCount - set max delivery attempts before message will discard or re-route to a new queue 127 | func (qm *QueueMessage) SetPolicyMaxReceiveCount(max int) *QueueMessage { 128 | if qm.Policy == nil { 129 | qm.Policy = &pb.QueueMessagePolicy{} 130 | } 131 | qm.Policy.MaxReceiveCount = int32(max) 132 | return qm 133 | } 134 | 135 | // SetPolicyMaxReceiveQueue - set queue name to be routed once MaxReceiveCount is triggered, empty will discard the message 136 | func (qm *QueueMessage) SetPolicyMaxReceiveQueue(channel string) *QueueMessage { 137 | if qm.Policy == nil { 138 | qm.Policy = &pb.QueueMessagePolicy{} 139 | } 140 | qm.Policy.MaxReceiveQueue = channel 141 | return qm 142 | } 143 | 144 | func (qm *QueueMessage) Ack() error { 145 | if qm.isCompleted.Load() { 146 | return fmt.Errorf("message already completed, reason: %s", qm.completeReason) 147 | } 148 | qm.isCompleted.Store(true) 149 | qm.completeReason = "ack" 150 | qm.stopVisibilityTimer() 151 | if qm.responseHandler == nil { 152 | return fmt.Errorf("function not valid") 153 | } 154 | 155 | return qm.responseHandler.AckOffsets(int64(qm.Attributes.Sequence)) 156 | } 157 | func (qm *QueueMessage) NAck() error { 158 | if qm.isCompleted.Load() { 159 | return fmt.Errorf("message already completed, reason: %s", qm.completeReason) 160 | } 161 | qm.isCompleted.Store(true) 162 | qm.completeReason = "nack" 163 | qm.stopVisibilityTimer() 164 | if qm.responseHandler == nil { 165 | return fmt.Errorf("function not valid") 166 | } 167 | return qm.responseHandler.NAckOffsets(int64(qm.Attributes.Sequence)) 168 | } 169 | func (qm *QueueMessage) ReQueue(channel string) error { 170 | if qm.isCompleted.Load() { 171 | return fmt.Errorf("message already completed, reason: %s", qm.completeReason) 172 | } 173 | qm.isCompleted.Store(true) 174 | qm.completeReason = "requeue" 175 | qm.stopVisibilityTimer() 176 | if qm.responseHandler == nil { 177 | return fmt.Errorf("function not valid") 178 | } 179 | return qm.responseHandler.ReQueueOffsets(channel, int64(qm.Attributes.Sequence)) 180 | } 181 | 182 | func (qm *QueueMessage) nackOnVisibility() error { 183 | if qm.isCompleted.Load() { 184 | return fmt.Errorf("message already completed, reason: %s", qm.completeReason) 185 | } 186 | qm.isCompleted.Store(true) 187 | qm.completeReason = "visibility timeout" 188 | qm.stopVisibilityTimer() 189 | if qm.responseHandler == nil { 190 | return fmt.Errorf("function not valid") 191 | } 192 | return qm.responseHandler.NAckOffsets(int64(qm.Attributes.Sequence)) 193 | } 194 | func (qm *QueueMessage) setCompleted(reason string) { 195 | qm.isCompleted.Store(true) 196 | qm.completeReason = reason 197 | qm.stopVisibilityTimer() 198 | } 199 | func (qm *QueueMessage) ExtendVisibility(visibilitySeconds int) error { 200 | if qm.isCompleted.Load() { 201 | return fmt.Errorf("message already completed") 202 | } 203 | if visibilitySeconds < 1 { 204 | return fmt.Errorf("visibility seconds must be greater than 0") 205 | } 206 | qm.mu.Lock() 207 | defer qm.mu.Unlock() 208 | if qm.visibilityDuration == 0 { 209 | return fmt.Errorf("visibility timer not set for this message") 210 | } 211 | if qm.visibilityTimer != nil { 212 | isStopped := qm.visibilityTimer.Stop() 213 | if !isStopped { 214 | return fmt.Errorf("visibility timer already expired") 215 | } 216 | qm.visibilityTimer.Reset(time.Duration(visibilitySeconds) * time.Second) 217 | } 218 | return nil 219 | } 220 | func (qm *QueueMessage) startVisibilityTimer() { 221 | qm.mu.Lock() 222 | defer qm.mu.Unlock() 223 | 224 | if qm.visibilityDuration == 0 { 225 | return 226 | } 227 | if qm.visibilityTimer != nil { 228 | qm.visibilityTimer.Stop() 229 | } 230 | qm.visibilityTimer = time.AfterFunc(qm.visibilityDuration, func() { 231 | _ = qm.nackOnVisibility() 232 | }) 233 | 234 | } 235 | func (qm *QueueMessage) stopVisibilityTimer() { 236 | if qm.visibilityDuration == 0 { 237 | return 238 | } 239 | qm.mu.Lock() 240 | defer qm.mu.Unlock() 241 | if qm.visibilityTimer != nil { 242 | qm.visibilityTimer.Stop() 243 | } 244 | } 245 | 246 | func (qm *QueueMessage) String() string { 247 | return fmt.Sprintf("Id: %s, ClientId: %s, Channel: %s, Metadata: %s, Body: %s, Tags: %s, Policy: %s, Attributes: %s", 248 | qm.MessageID, qm.ClientID, qm.Channel, qm.Metadata, qm.Body, qm.Tags, policyToString(qm.Policy), attributesToString(qm.Attributes)) 249 | } 250 | 251 | func policyToString(policy *pb.QueueMessagePolicy) string { 252 | if policy == nil { 253 | return "" 254 | } 255 | return fmt.Sprintf("ExpirationSeconds: %d, DelaySeconds: %d, MaxReceiveCount: %d, MaxReceiveQueue: %s", 256 | policy.ExpirationSeconds, policy.DelaySeconds, policy.MaxReceiveCount, policy.MaxReceiveQueue) 257 | } 258 | 259 | func attributesToString(attributes *pb.QueueMessageAttributes) string { 260 | if attributes == nil { 261 | return "" 262 | } 263 | return fmt.Sprintf("Sequence: %d, Timestamp: %s, ReceiveCount: %d, ReRouted: %t, ReRoutedFromQueue: %s, ExpirationAt: %s, DelayedTo: %s", 264 | attributes.Sequence, time.Unix(attributes.Timestamp, 0).String(), attributes.ReceiveCount, attributes.ReRouted, attributes.ReRoutedFromQueue, time.Unix(attributes.ExpirationAt, 0).String(), time.Unix(attributes.DelayedTo, 0).String()) 265 | } 266 | -------------------------------------------------------------------------------- /queues_stream/options.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | const kubeMQAuthTokenHeader = "authorization" 9 | 10 | type Option interface { 11 | apply(*Options) 12 | } 13 | 14 | type Options struct { 15 | host string 16 | port int 17 | isSecured bool 18 | certFile string 19 | certData string 20 | serverOverrideDomain string 21 | authToken string 22 | clientId string 23 | receiveBufferSize int 24 | defaultChannel string 25 | defaultCacheTTL time.Duration 26 | restUri string 27 | webSocketUri string 28 | autoReconnect bool 29 | reconnectInterval time.Duration 30 | maxReconnect int 31 | checkConnection bool 32 | connectionNotificationFunc func(msg string) 33 | } 34 | 35 | type funcOptions struct { 36 | fn func(*Options) 37 | } 38 | 39 | func (fo *funcOptions) apply(o *Options) { 40 | fo.fn(o) 41 | } 42 | 43 | func newFuncOption(f func(*Options)) *funcOptions { 44 | return &funcOptions{ 45 | fn: f, 46 | } 47 | } 48 | 49 | // WithAddress - set host and port address of KubeMQ server 50 | func WithAddress(host string, port int) Option { 51 | return newFuncOption(func(o *Options) { 52 | o.host = host 53 | o.port = port 54 | 55 | }) 56 | } 57 | 58 | // WithConnectionNotificationFunc - set on connection activity messages 59 | func WithConnectionNotificationFunc(fn func(msg string)) Option { 60 | return newFuncOption(func(o *Options) { 61 | o.connectionNotificationFunc = fn 62 | }) 63 | } 64 | 65 | // WithCredentials - set secured TLS credentials from the input certificate file for grpcClient. 66 | // serverNameOverride is for testing only. If set to a non empty string, 67 | // it will override the virtual host name of authority (e.g. :authority header field) in requests. 68 | func WithCredentials(certFile, serverOverrideDomain string) Option { 69 | return newFuncOption(func(o *Options) { 70 | o.isSecured = true 71 | o.certFile = certFile 72 | o.serverOverrideDomain = serverOverrideDomain 73 | }) 74 | } 75 | 76 | // WithCertificate - set secured TLS credentials from the input certificate data for grpcClient. 77 | // serverNameOverride is for testing only. If set to a non empty string, 78 | // it will override the virtual host name of authority (e.g. :authority header field) in requests. 79 | func WithCertificate(certData, serverOverrideDomain string) Option { 80 | return newFuncOption(func(o *Options) { 81 | o.isSecured = true 82 | o.certData = certData 83 | o.serverOverrideDomain = serverOverrideDomain 84 | }) 85 | } 86 | 87 | // WithAuthToken - set KubeMQ JWT Auth token to be used for KubeMQ connection 88 | func WithAuthToken(token string) Option { 89 | return newFuncOption(func(o *Options) { 90 | o.authToken = token 91 | }) 92 | } 93 | 94 | // WithClientId - set grpcClient id to be used in all functions call with this grpcClient - mandatory 95 | func WithClientId(id string) Option { 96 | return newFuncOption(func(o *Options) { 97 | o.clientId = id 98 | }) 99 | } 100 | 101 | // WithReceiveBufferSize - set length of buffered channel to be set in all subscriptions 102 | func WithReceiveBufferSize(size int) Option { 103 | return newFuncOption(func(o *Options) { 104 | o.receiveBufferSize = size 105 | }) 106 | } 107 | 108 | // WithDefaultChannel - set default channel for any outbound requests 109 | func WithDefaultChannel(channel string) Option { 110 | return newFuncOption(func(o *Options) { 111 | o.defaultChannel = channel 112 | }) 113 | } 114 | 115 | // WithDefaultCacheTTL - set default cache time to live for any query requests with any CacheKey set value 116 | func WithDefaultCacheTTL(ttl time.Duration) Option { 117 | return newFuncOption(func(o *Options) { 118 | o.defaultCacheTTL = ttl 119 | }) 120 | } 121 | 122 | // WithAutoReconnect - set automatic reconnection in case of lost connectivity to server 123 | func WithAutoReconnect(value bool) Option { 124 | return newFuncOption(func(o *Options) { 125 | o.autoReconnect = value 126 | }) 127 | } 128 | 129 | // WithReconnectInterval - set reconnection interval duration, default is 5 seconds 130 | func WithReconnectInterval(duration time.Duration) Option { 131 | return newFuncOption(func(o *Options) { 132 | o.reconnectInterval = duration 133 | }) 134 | } 135 | 136 | // WithMaxReconnects - set max reconnects before return error, default 0, never. 137 | func WithMaxReconnects(value int) Option { 138 | return newFuncOption(func(o *Options) { 139 | o.maxReconnect = value 140 | }) 141 | } 142 | 143 | // WithCheckConnection - set server connectivity on grpcClient create 144 | func WithCheckConnection(value bool) Option { 145 | return newFuncOption(func(o *Options) { 146 | o.checkConnection = value 147 | }) 148 | } 149 | 150 | func GetDefaultOptions() *Options { 151 | return &Options{ 152 | host: "", 153 | port: 0, 154 | isSecured: false, 155 | certFile: "", 156 | certData: "", 157 | serverOverrideDomain: "", 158 | authToken: "", 159 | clientId: "ClientId", 160 | receiveBufferSize: 10, 161 | defaultChannel: "", 162 | defaultCacheTTL: time.Minute * 15, 163 | restUri: "", 164 | webSocketUri: "", 165 | autoReconnect: false, 166 | reconnectInterval: 5 * time.Second, 167 | maxReconnect: 0, 168 | checkConnection: false, 169 | } 170 | } 171 | 172 | func (o *Options) Validate() error { 173 | 174 | if o.host == "" { 175 | return errors.New("invalid host") 176 | } 177 | if o.port <= 0 { 178 | return errors.New("invalid port") 179 | } 180 | 181 | return nil 182 | } 183 | -------------------------------------------------------------------------------- /queues_stream/poll_request.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubemq-io/kubemq-go/pkg/uuid" 6 | pb "github.com/kubemq-io/protobuf/go" 7 | ) 8 | 9 | // PollRequest - Request parameters for Poll function 10 | type PollRequest struct { 11 | Channel string `json:"channel"` 12 | MaxItems int `json:"max_items"` 13 | WaitTimeout int `json:"wait_timeout"` 14 | AutoAck bool `json:"auto_ack"` 15 | VisibilitySeconds int `json:"visibility_seconds"` 16 | OnErrorFunc func(err error) 17 | OnComplete func() 18 | } 19 | 20 | func (p *PollRequest) SetOnErrorFunc(onErrorFunc func(err error)) *PollRequest { 21 | p.OnErrorFunc = onErrorFunc 22 | return p 23 | } 24 | 25 | func (p *PollRequest) SetOnComplete(onComplete func()) *PollRequest { 26 | p.OnComplete = onComplete 27 | return p 28 | } 29 | 30 | func NewPollRequest() *PollRequest { 31 | return &PollRequest{} 32 | } 33 | func (p *PollRequest) SetChannel(channel string) *PollRequest { 34 | p.Channel = channel 35 | return p 36 | } 37 | 38 | func (p *PollRequest) SetMaxItems(maxItems int) *PollRequest { 39 | p.MaxItems = maxItems 40 | return p 41 | } 42 | 43 | func (p *PollRequest) SetWaitTimeout(waitTimeout int) *PollRequest { 44 | p.WaitTimeout = waitTimeout 45 | return p 46 | } 47 | func (p *PollRequest) SetWaitTimeoutSeconds(waitTimeout int) *PollRequest { 48 | p.WaitTimeout = waitTimeout * 1000 49 | return p 50 | } 51 | 52 | func (p *PollRequest) SetAutoAck(autoAck bool) *PollRequest { 53 | p.AutoAck = autoAck 54 | return p 55 | } 56 | 57 | func (p *PollRequest) SetVisibilitySeconds(visibilitySeconds int) *PollRequest { 58 | p.VisibilitySeconds = visibilitySeconds 59 | return p 60 | } 61 | func (p *PollRequest) validateAndComplete(clientId string) (*pb.QueuesDownstreamRequest, error) { 62 | if p.Channel == "" { 63 | return nil, fmt.Errorf("request channel cannot be empty") 64 | } 65 | if p.MaxItems < 0 { 66 | return nil, fmt.Errorf("request max items cannot be negative") 67 | } 68 | if p.WaitTimeout < 0 { 69 | return nil, fmt.Errorf("request wait timeout cannot be negative") 70 | } 71 | if p.VisibilitySeconds < 0 { 72 | return nil, fmt.Errorf("request visibility seconds cannot be negative") 73 | } 74 | if p.AutoAck && p.VisibilitySeconds > 0 { 75 | return nil, fmt.Errorf("request visibility seconds cannot be set with auto ack") 76 | } 77 | requestClientId := clientId 78 | if requestClientId == "" { 79 | requestClientId = uuid.New() 80 | } 81 | return &pb.QueuesDownstreamRequest{ 82 | RequestID: uuid.New(), 83 | ClientID: requestClientId, 84 | RequestTypeData: pb.QueuesDownstreamRequestType_Get, 85 | Channel: p.Channel, 86 | MaxItems: int32(p.MaxItems), 87 | WaitTimeout: int32(p.WaitTimeout), 88 | AutoAck: p.AutoAck, 89 | }, nil 90 | } 91 | -------------------------------------------------------------------------------- /queues_stream/poll_response.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import pb "github.com/kubemq-io/protobuf/go" 4 | 5 | type PollResponse struct { 6 | Messages []*QueueMessage 7 | *responseHandler 8 | } 9 | 10 | func newPollResponse(messages []*pb.QueueMessage, handler *responseHandler) *PollResponse { 11 | p := &PollResponse{ 12 | Messages: nil, 13 | responseHandler: handler, 14 | } 15 | for _, message := range messages { 16 | p.Messages = append(p.Messages, newQueueMessageFrom(message).setResponseHandler(handler)) 17 | } 18 | p.responseHandler.setIsEmptyResponse(len(p.Messages) == 0) 19 | p.responseHandler.setMessages(p.Messages) 20 | return p 21 | } 22 | 23 | func (p *PollResponse) HasMessages() bool { 24 | return len(p.Messages) > 0 25 | } 26 | -------------------------------------------------------------------------------- /queues_stream/queue_info.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import pb "github.com/kubemq-io/protobuf/go" 4 | 5 | type QueueInfo struct { 6 | Name string `json:"name"` 7 | Messages int64 `json:"messages"` 8 | Bytes int64 `json:"bytes"` 9 | FirstSequence int64 `json:"first_sequence"` 10 | LastSequence int64 `json:"last_sequence"` 11 | Sent int64 `json:"sent"` 12 | Subscribers int `json:"subscribers"` 13 | Waiting int64 `json:"waiting"` 14 | Delivered int64 `json:"delivered"` 15 | } 16 | type QueuesInfo struct { 17 | TotalQueues int `json:"total_queues"` 18 | Sent int64 `json:"sent"` 19 | Waiting int64 `json:"waiting"` 20 | Delivered int64 `json:"delivered"` 21 | Queues []*QueueInfo `json:"queues"` 22 | } 23 | 24 | func fromQueuesInfoPb(info *pb.QueuesInfo) *QueuesInfo { 25 | q := &QueuesInfo{ 26 | TotalQueues: int(info.TotalQueue), 27 | Sent: info.Sent, 28 | Waiting: info.Waiting, 29 | Delivered: info.Delivered, 30 | Queues: nil, 31 | } 32 | for _, queue := range info.Queues { 33 | q.Queues = append(q.Queues, &QueueInfo{ 34 | Name: queue.Name, 35 | Messages: queue.Messages, 36 | Bytes: queue.Bytes, 37 | FirstSequence: queue.FirstSequence, 38 | LastSequence: queue.LastSequence, 39 | Sent: queue.Sent, 40 | Subscribers: int(queue.Subscribers), 41 | Waiting: queue.Waiting, 42 | Delivered: queue.Delivered, 43 | }) 44 | } 45 | return q 46 | } 47 | -------------------------------------------------------------------------------- /queues_stream/response_handler.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/kubemq-io/kubemq-go/pkg/uuid" 7 | pb "github.com/kubemq-io/protobuf/go" 8 | "go.uber.org/atomic" 9 | "time" 10 | ) 11 | 12 | const requestTimout = 60 * time.Second 13 | 14 | type responseHandler struct { 15 | handlerCtx context.Context 16 | handlerCancel context.CancelFunc 17 | requestCh chan *pb.QueuesDownstreamRequest 18 | responseCh chan *pb.QueuesDownstreamResponse 19 | errCh chan error 20 | isActive *atomic.Bool 21 | requestClientId string 22 | requestChannel string 23 | transactionId string 24 | requestId string 25 | isEmptyResponse bool 26 | visibilitySeconds int 27 | isAutoAck bool 28 | onErrorFunc func(err error) 29 | onCompleteFunc func() 30 | messages []*QueueMessage 31 | } 32 | 33 | func (r *responseHandler) setIsEmptyResponse(isEmptyResponse bool) *responseHandler { 34 | r.isEmptyResponse = isEmptyResponse 35 | return nil 36 | } 37 | 38 | func newResponseHandler() *responseHandler { 39 | return &responseHandler{ 40 | handlerCtx: nil, 41 | handlerCancel: nil, 42 | requestCh: nil, 43 | responseCh: make(chan *pb.QueuesDownstreamResponse, 1), 44 | errCh: make(chan error, 10), 45 | isActive: atomic.NewBool(false), 46 | requestClientId: "", 47 | requestChannel: "", 48 | transactionId: "", 49 | requestId: "", 50 | isEmptyResponse: false, 51 | onErrorFunc: nil, 52 | onCompleteFunc: nil, 53 | } 54 | } 55 | 56 | func (r *responseHandler) Close() error { 57 | request := &pb.QueuesDownstreamRequest{ 58 | RequestID: uuid.New(), 59 | ClientID: r.requestClientId, 60 | RequestTypeData: pb.QueuesDownstreamRequestType_CloseByClient, 61 | Channel: r.requestChannel, 62 | MaxItems: 0, 63 | WaitTimeout: 0, 64 | AutoAck: false, 65 | ReQueueChannel: "", 66 | SequenceRange: nil, 67 | RefTransactionId: r.transactionId, 68 | } 69 | err := r.sendRequest(request) 70 | if err != nil { 71 | return err 72 | } 73 | return nil 74 | } 75 | func (r *responseHandler) start(ctx context.Context) { 76 | r.handlerCtx, r.handlerCancel = context.WithCancel(ctx) 77 | r.isActive.Store(true) 78 | for _, msg := range r.messages { 79 | msg.startVisibilityTimer() 80 | 81 | } 82 | } 83 | func (r *responseHandler) sendRequest(request *pb.QueuesDownstreamRequest) error { 84 | if !r.isActive.Load() { 85 | return fmt.Errorf("transaction is not ready to accept requests or already closed by AutoAck") 86 | } 87 | select { 88 | case r.requestCh <- request: 89 | //log.Println("sending request", request.RequestTypeData, request.RequestID) 90 | case <-time.After(requestTimout): 91 | return fmt.Errorf("request send timeout") 92 | case <-r.handlerCtx.Done(): 93 | //log.Println("response ctx handler canceled", r.transactionId) 94 | return r.handlerCtx.Err() 95 | } 96 | return nil 97 | } 98 | func (r *responseHandler) setRequestCh(requestCh chan *pb.QueuesDownstreamRequest) *responseHandler { 99 | r.requestCh = requestCh 100 | return r 101 | } 102 | 103 | func (r *responseHandler) setRequestClientId(requestClientId string) *responseHandler { 104 | r.requestClientId = requestClientId 105 | return r 106 | } 107 | func (r *responseHandler) setRequestChanel(requestChannel string) *responseHandler { 108 | r.requestChannel = requestChannel 109 | return r 110 | } 111 | func (r *responseHandler) setTransactionId(transactionId string) *responseHandler { 112 | r.transactionId = transactionId 113 | return r 114 | } 115 | 116 | func (r *responseHandler) setRequestId(requestId string) *responseHandler { 117 | r.requestId = requestId 118 | return r 119 | } 120 | func (r *responseHandler) setOnErrorFunc(onErrorFunc func(err error)) *responseHandler { 121 | r.onErrorFunc = onErrorFunc 122 | return r 123 | } 124 | 125 | func (r *responseHandler) setOnCompleteFunc(onCloseFunc func()) *responseHandler { 126 | 127 | r.onCompleteFunc = onCloseFunc 128 | return r 129 | } 130 | func (r *responseHandler) sendError(err error) { 131 | if r.onErrorFunc != nil { 132 | r.onErrorFunc(err) 133 | } 134 | } 135 | func (r *responseHandler) setVisibilitySeconds(visibilitySeconds int) *responseHandler { 136 | r.visibilitySeconds = visibilitySeconds 137 | return r 138 | } 139 | 140 | func (r *responseHandler) setIsAutoAck(isAutoAck bool) *responseHandler { 141 | r.isAutoAck = isAutoAck 142 | return r 143 | } 144 | func (r *responseHandler) setMessages(messages []*QueueMessage) *responseHandler { 145 | r.messages = messages 146 | return r 147 | } 148 | func (r *responseHandler) sendComplete() { 149 | 150 | r.isActive.Store(false) 151 | if r.handlerCancel != nil { 152 | r.handlerCancel() 153 | } 154 | if r.onCompleteFunc != nil { 155 | r.onCompleteFunc() 156 | } 157 | } 158 | func (r *responseHandler) AckAll() error { 159 | if r.isEmptyResponse { 160 | return fmt.Errorf("no available messages to ack") 161 | } 162 | request := &pb.QueuesDownstreamRequest{ 163 | RequestID: uuid.New(), 164 | ClientID: r.requestClientId, 165 | RequestTypeData: pb.QueuesDownstreamRequestType_AckAll, 166 | Channel: r.requestChannel, 167 | MaxItems: 0, 168 | WaitTimeout: 0, 169 | AutoAck: false, 170 | ReQueueChannel: "", 171 | SequenceRange: nil, 172 | RefTransactionId: r.transactionId, 173 | } 174 | err := r.sendRequest(request) 175 | if err != nil { 176 | return err 177 | } 178 | r.markCompleted("ack all") 179 | return nil 180 | } 181 | func (r *responseHandler) AckOffsets(offsets ...int64) error { 182 | if r.isEmptyResponse { 183 | return fmt.Errorf("no available messages to ack") 184 | } 185 | if len(offsets) == 0 { 186 | return fmt.Errorf("no available offsets messages to ack") 187 | } 188 | for i := 0; i < len(offsets); i++ { 189 | if offsets[i] <= 0 { 190 | return fmt.Errorf("invalid offset %d for ack, must be greater than 0", offsets[i]) 191 | } 192 | } 193 | request := &pb.QueuesDownstreamRequest{ 194 | RequestID: uuid.New(), 195 | ClientID: r.requestClientId, 196 | RequestTypeData: pb.QueuesDownstreamRequestType_AckRange, 197 | Channel: r.requestChannel, 198 | MaxItems: 0, 199 | WaitTimeout: 0, 200 | AutoAck: false, 201 | ReQueueChannel: "", 202 | SequenceRange: offsets, 203 | RefTransactionId: r.transactionId, 204 | } 205 | err := r.sendRequest(request) 206 | if err != nil { 207 | return err 208 | } 209 | return nil 210 | } 211 | 212 | func (r *responseHandler) NAckAll() error { 213 | if r.isEmptyResponse { 214 | return fmt.Errorf("no available messages to nack") 215 | } 216 | request := &pb.QueuesDownstreamRequest{ 217 | RequestID: uuid.New(), 218 | ClientID: r.requestClientId, 219 | RequestTypeData: pb.QueuesDownstreamRequestType_NAckAll, 220 | Channel: r.requestChannel, 221 | MaxItems: 0, 222 | WaitTimeout: 0, 223 | AutoAck: false, 224 | ReQueueChannel: "", 225 | SequenceRange: nil, 226 | RefTransactionId: r.transactionId, 227 | } 228 | err := r.sendRequest(request) 229 | if err != nil { 230 | return err 231 | } 232 | r.markCompleted("nack all") 233 | return nil 234 | } 235 | func (r *responseHandler) NAckOffsets(offsets ...int64) error { 236 | if r.isEmptyResponse { 237 | return fmt.Errorf("no available messages to nack") 238 | } 239 | if len(offsets) == 0 { 240 | return fmt.Errorf("no available offsets messages to nack") 241 | } 242 | for i := 0; i < len(offsets); i++ { 243 | if offsets[i] <= 0 { 244 | return fmt.Errorf("invalid offset %d for nack, must be greater than 0", offsets[i]) 245 | } 246 | } 247 | request := &pb.QueuesDownstreamRequest{ 248 | RequestID: uuid.New(), 249 | ClientID: r.requestClientId, 250 | RequestTypeData: pb.QueuesDownstreamRequestType_NAckRange, 251 | Channel: r.requestChannel, 252 | MaxItems: 0, 253 | WaitTimeout: 0, 254 | AutoAck: false, 255 | ReQueueChannel: "", 256 | SequenceRange: offsets, 257 | RefTransactionId: r.transactionId, 258 | } 259 | err := r.sendRequest(request) 260 | if err != nil { 261 | return err 262 | } 263 | return nil 264 | } 265 | func (r *responseHandler) ReQueueAll(channel string) error { 266 | if r.isEmptyResponse { 267 | return fmt.Errorf("no available messages to requeue") 268 | } 269 | if channel == "" { 270 | return fmt.Errorf("requeue channel cannot be empty") 271 | } 272 | request := &pb.QueuesDownstreamRequest{ 273 | RequestID: uuid.New(), 274 | ClientID: r.requestClientId, 275 | RequestTypeData: pb.QueuesDownstreamRequestType_ReQueueAll, 276 | Channel: r.requestChannel, 277 | MaxItems: 0, 278 | WaitTimeout: 0, 279 | AutoAck: false, 280 | ReQueueChannel: channel, 281 | SequenceRange: nil, 282 | RefTransactionId: r.transactionId, 283 | } 284 | err := r.sendRequest(request) 285 | if err != nil { 286 | return err 287 | } 288 | r.markCompleted("requeue all") 289 | return nil 290 | } 291 | func (r *responseHandler) ReQueueOffsets(channel string, offsets ...int64) error { 292 | if r.isEmptyResponse { 293 | return fmt.Errorf("no available messages to requeue") 294 | } 295 | if len(offsets) == 0 { 296 | return fmt.Errorf("no available offsets messages to requeue") 297 | } 298 | for i := 0; i < len(offsets); i++ { 299 | if offsets[i] <= 0 { 300 | return fmt.Errorf("invalid offset %d for requeue, must be greater than 0", offsets[i]) 301 | } 302 | } 303 | if channel == "" { 304 | return fmt.Errorf("requeue channel cannot be empty") 305 | } 306 | request := &pb.QueuesDownstreamRequest{ 307 | RequestID: uuid.New(), 308 | ClientID: r.requestClientId, 309 | RequestTypeData: pb.QueuesDownstreamRequestType_ReQueueRange, 310 | Channel: r.requestChannel, 311 | MaxItems: 0, 312 | WaitTimeout: 0, 313 | AutoAck: false, 314 | ReQueueChannel: channel, 315 | SequenceRange: offsets, 316 | RefTransactionId: r.transactionId, 317 | } 318 | err := r.sendRequest(request) 319 | if err != nil { 320 | return err 321 | } 322 | return nil 323 | } 324 | 325 | func (r *responseHandler) markCompleted(reason string) { 326 | for _, msg := range r.messages { 327 | msg.setCompleted(reason) 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /queues_stream/send_result.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "fmt" 5 | pb "github.com/kubemq-io/protobuf/go" 6 | "strings" 7 | ) 8 | 9 | type SendResult struct { 10 | Results []*pb.SendQueueMessageResult 11 | } 12 | 13 | func newSendResult(results []*pb.SendQueueMessageResult) *SendResult { 14 | s := &SendResult{ 15 | Results: results, 16 | } 17 | return s 18 | } 19 | 20 | func resultToString(result *pb.SendQueueMessageResult) string { 21 | return fmt.Sprintf("MessageID: %s, Error: %s\n", result.MessageID, result.Error) 22 | } 23 | func (s *SendResult) String() string { 24 | sb := strings.Builder{} 25 | for _, result := range s.Results { 26 | sb.WriteString(resultToString(result)) 27 | } 28 | return sb.String() 29 | } 30 | -------------------------------------------------------------------------------- /queues_stream/subscribe_request.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kubemq-io/kubemq-go/pkg/uuid" 6 | pb "github.com/kubemq-io/protobuf/go" 7 | ) 8 | 9 | type SubscribeRequest struct { 10 | Channels []string 11 | MaxItems int `json:"max_items"` 12 | WaitTimeout int `json:"wait_timeout"` 13 | AutoAck bool `json:"auto_ack"` 14 | } 15 | 16 | func NewSubscribeRequest() *SubscribeRequest { 17 | return &SubscribeRequest{} 18 | } 19 | 20 | func (s *SubscribeRequest) SetChannels(channels ...string) *SubscribeRequest { 21 | s.Channels = channels 22 | return s 23 | } 24 | 25 | func (s *SubscribeRequest) SetMaxItems(maxItems int) *SubscribeRequest { 26 | s.MaxItems = maxItems 27 | return s 28 | } 29 | 30 | func (s *SubscribeRequest) SetWaitTimeout(waitTimeout int) *SubscribeRequest { 31 | s.WaitTimeout = waitTimeout 32 | return s 33 | } 34 | 35 | func (s *SubscribeRequest) SetAutoAck(autoAck bool) *SubscribeRequest { 36 | s.AutoAck = autoAck 37 | return s 38 | } 39 | 40 | func (s *SubscribeRequest) validateAndComplete(clientId string) ([]*pb.QueuesDownstreamRequest, error) { 41 | 42 | if len(s.Channels) == 0 { 43 | return nil, fmt.Errorf("request channels cannot be empty") 44 | } 45 | for i, channel := range s.Channels { 46 | if channel == "" { 47 | return nil, fmt.Errorf("request channel %d cannot be empty", i) 48 | } 49 | } 50 | if s.MaxItems < 0 { 51 | return nil, fmt.Errorf("request max items cannot be negative") 52 | } 53 | if s.WaitTimeout < 0 { 54 | return nil, fmt.Errorf("request wait timeout cannot be negative") 55 | } 56 | requestClientId := clientId 57 | if requestClientId == "" { 58 | requestClientId = uuid.New() 59 | } 60 | var requests []*pb.QueuesDownstreamRequest 61 | for _, channel := range s.Channels { 62 | requests = append(requests, &pb.QueuesDownstreamRequest{ 63 | RequestID: uuid.New(), 64 | ClientID: requestClientId, 65 | RequestTypeData: pb.QueuesDownstreamRequestType_Get, 66 | Channel: channel, 67 | MaxItems: int32(s.MaxItems), 68 | WaitTimeout: int32(s.WaitTimeout), 69 | AutoAck: s.AutoAck, 70 | }) 71 | } 72 | return requests, nil 73 | } 74 | -------------------------------------------------------------------------------- /queues_stream/subscribe_response.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | type SubscribeResponse struct { 4 | Messages []*QueueMessage 5 | *responseHandler 6 | } 7 | 8 | func NewSubscribeResponse() *SubscribeResponse { 9 | return &SubscribeResponse{} 10 | } 11 | func (s *SubscribeResponse) setMessages(messages []*QueueMessage) *SubscribeResponse { 12 | s.Messages = messages 13 | return s 14 | } 15 | func (s *SubscribeResponse) setResponseHandler(handler *responseHandler) *SubscribeResponse { 16 | s.responseHandler = handler 17 | return s 18 | } 19 | -------------------------------------------------------------------------------- /queues_stream/upstream.go: -------------------------------------------------------------------------------- 1 | package queues_stream 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | pb "github.com/kubemq-io/protobuf/go" 7 | "go.uber.org/atomic" 8 | "io" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type upstream struct { 14 | sync.Mutex 15 | upstreamCtx context.Context 16 | upstreamCancel context.CancelFunc 17 | activeTransactions map[string]chan *pb.QueuesUpstreamResponse 18 | requestCh chan *pb.QueuesUpstreamRequest 19 | responseCh chan *pb.QueuesUpstreamResponse 20 | errCh chan error 21 | doneCh chan bool 22 | grpcClient pb.KubemqClient 23 | streamClient *QueuesStreamClient 24 | isClosed bool 25 | connectionState *atomic.Bool 26 | } 27 | 28 | func newUpstream(ctx context.Context, streamClient *QueuesStreamClient) *upstream { 29 | 30 | u := &upstream{ 31 | Mutex: sync.Mutex{}, 32 | activeTransactions: map[string]chan *pb.QueuesUpstreamResponse{}, 33 | errCh: make(chan error, 10), 34 | requestCh: make(chan *pb.QueuesUpstreamRequest, 10), 35 | responseCh: make(chan *pb.QueuesUpstreamResponse, 10), 36 | doneCh: make(chan bool, 1), 37 | grpcClient: streamClient.client.KubemqClient, 38 | streamClient: streamClient, 39 | connectionState: atomic.NewBool(false), 40 | } 41 | u.upstreamCtx, u.upstreamCancel = context.WithCancel(ctx) 42 | go u.run() 43 | time.Sleep(time.Second) 44 | return u 45 | } 46 | func (u *upstream) sendOnConnectionState(msg string) { 47 | if u.streamClient.client.opts.connectionNotificationFunc != nil { 48 | go func() { 49 | u.streamClient.client.opts.connectionNotificationFunc(msg) 50 | }() 51 | } 52 | } 53 | func (u *upstream) close() { 54 | u.setIsClose(true) 55 | u.upstreamCancel() 56 | } 57 | func (u *upstream) setIsClose(value bool) { 58 | u.Lock() 59 | defer u.Unlock() 60 | u.isClosed = value 61 | } 62 | func (u *upstream) getIsClose() bool { 63 | u.Lock() 64 | defer u.Unlock() 65 | return u.isClosed 66 | } 67 | 68 | func (u *upstream) setTransaction(id string) chan *pb.QueuesUpstreamResponse { 69 | u.Lock() 70 | defer u.Unlock() 71 | respCh := make(chan *pb.QueuesUpstreamResponse, 1) 72 | u.activeTransactions[id] = respCh 73 | return respCh 74 | } 75 | func (u *upstream) getTransaction(id string) (chan *pb.QueuesUpstreamResponse, bool) { 76 | u.Lock() 77 | defer u.Unlock() 78 | respCh, ok := u.activeTransactions[id] 79 | return respCh, ok 80 | 81 | } 82 | func (u *upstream) deleteTransaction(id string) { 83 | u.Lock() 84 | defer u.Unlock() 85 | delete(u.activeTransactions, id) 86 | } 87 | func (u *upstream) connectStream(ctx context.Context) { 88 | defer func() { 89 | u.doneCh <- true 90 | u.connectionState.Store(false) 91 | u.sendOnConnectionState("grpc queue client upstream disconnected") 92 | }() 93 | stream, err := u.grpcClient.QueuesUpstream(ctx) 94 | if err != nil { 95 | u.errCh <- err 96 | u.sendOnConnectionState(fmt.Sprintf("grpc queue client upstream connection error, %s", err.Error())) 97 | return 98 | } 99 | u.connectionState.Store(true) 100 | u.sendOnConnectionState("grpc queue client upstream connected") 101 | go func() { 102 | for { 103 | res, err := stream.Recv() 104 | if err != nil { 105 | if err == io.EOF { 106 | return 107 | } 108 | u.errCh <- err 109 | u.sendOnConnectionState(fmt.Sprintf("grpc queue client upstream receive error, %s", err.Error())) 110 | return 111 | } 112 | select { 113 | case u.responseCh <- res: 114 | case <-stream.Context().Done(): 115 | return 116 | case <-ctx.Done(): 117 | return 118 | } 119 | } 120 | }() 121 | 122 | for { 123 | select { 124 | case req := <-u.requestCh: 125 | err := stream.Send(req) 126 | if err != nil { 127 | if err == io.EOF { 128 | return 129 | } 130 | u.errCh <- err 131 | u.sendOnConnectionState(fmt.Sprintf("grpc queue client updatream send error, %s", err.Error())) 132 | return 133 | } 134 | case <-stream.Context().Done(): 135 | return 136 | case <-ctx.Done(): 137 | return 138 | } 139 | } 140 | 141 | } 142 | func (u *upstream) clearPendingTransactions(err error) { 143 | u.Lock() 144 | u.Unlock() 145 | for id, respCh := range u.activeTransactions { 146 | respCh <- &pb.QueuesUpstreamResponse{ 147 | RefRequestID: id, 148 | Results: nil, 149 | IsError: true, 150 | Error: err.Error(), 151 | } 152 | delete(u.activeTransactions, id) 153 | } 154 | } 155 | func (u *upstream) run() { 156 | for { 157 | if !u.getIsClose() { 158 | go u.connectStream(u.upstreamCtx) 159 | } else { 160 | return 161 | } 162 | for { 163 | select { 164 | case resp := <-u.responseCh: 165 | respCh, ok := u.getTransaction(resp.RefRequestID) 166 | if ok { 167 | respCh <- resp 168 | u.deleteTransaction(resp.RefRequestID) 169 | } 170 | case err := <-u.errCh: 171 | u.clearPendingTransactions(err) 172 | case <-u.doneCh: 173 | goto reconnect 174 | case <-u.upstreamCtx.Done(): 175 | u.clearPendingTransactions(u.upstreamCtx.Err()) 176 | return 177 | } 178 | } 179 | reconnect: 180 | time.Sleep(time.Second) 181 | } 182 | } 183 | func (u *upstream) isReady() bool { 184 | return u.connectionState.Load() 185 | } 186 | func (u *upstream) send(req *pb.QueuesUpstreamRequest) chan *pb.QueuesUpstreamResponse { 187 | respCh := u.setTransaction(req.RequestID) 188 | u.requestCh <- req 189 | return respCh 190 | } 191 | func (u *upstream) cancelTransaction(id string) { 192 | u.deleteTransaction(id) 193 | } 194 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | type Response struct { 9 | RequestId string 10 | ResponseTo string 11 | Metadata string 12 | Body []byte 13 | ClientId string 14 | ExecutedAt time.Time 15 | Err error 16 | Tags map[string]string 17 | transport Transport 18 | trace *Trace 19 | } 20 | 21 | func NewResponse() *Response { 22 | return &Response{} 23 | } 24 | 25 | // SetId - set response corresponded requestId - mandatory 26 | func (r *Response) SetRequestId(id string) *Response { 27 | r.RequestId = id 28 | return r 29 | } 30 | 31 | // SetResponseTo - set response channel as received in CommandReceived or QueryReceived object - mandatory 32 | func (r *Response) SetResponseTo(channel string) *Response { 33 | r.ResponseTo = channel 34 | return r 35 | } 36 | 37 | // SetMetadata - set metadata response, for query only 38 | func (r *Response) SetMetadata(metadata string) *Response { 39 | r.Metadata = metadata 40 | return r 41 | } 42 | 43 | // SetMetadata - set body response, for query only 44 | func (r *Response) SetBody(body []byte) *Response { 45 | r.Body = body 46 | return r 47 | } 48 | 49 | // SetTags - set response tags 50 | func (r *Response) SetTags(tags map[string]string) *Response { 51 | r.Tags = tags 52 | return r 53 | } 54 | 55 | // SetClientID - set clientId response, if not set default clientId will be used 56 | func (r *Response) SetClientId(clientId string) *Response { 57 | r.ClientId = clientId 58 | return r 59 | } 60 | 61 | // SetError - set query or command execution error 62 | func (r *Response) SetError(err error) *Response { 63 | r.Err = err 64 | return r 65 | } 66 | 67 | // SetExecutedAt - set query or command execution time 68 | func (r *Response) SetExecutedAt(executedAt time.Time) *Response { 69 | r.ExecutedAt = executedAt 70 | return r 71 | } 72 | 73 | // AddTrace - add tracing support to response 74 | func (r *Response) AddTrace(name string) *Trace { 75 | r.trace = CreateTrace(name) 76 | return r.trace 77 | } 78 | 79 | // Send - sending response to command or query request 80 | func (r *Response) Send(ctx context.Context) error { 81 | return r.transport.SendResponse(ctx, r) 82 | } 83 | 84 | func (r *Response) String() string { 85 | return "Response: RequestId: " + r.RequestId + ", ResponseTo: " + r.ResponseTo + ", Metadata: " + r.Metadata + ", Body: " + string(r.Body) + ", ClientId: " + r.ClientId + ", ExecutedAt: " + r.ExecutedAt.String() + ", Err: " + r.Err.Error() 86 | } 87 | -------------------------------------------------------------------------------- /trace.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "time" 5 | 6 | "go.opencensus.io/trace" 7 | ) 8 | 9 | type Trace struct { 10 | Name string 11 | attributes []trace.Attribute 12 | annotation trace.Annotation 13 | } 14 | 15 | func CreateTrace(name string) *Trace { 16 | t := &Trace{ 17 | Name: name, 18 | attributes: nil, 19 | annotation: trace.Annotation{}, 20 | } 21 | return t 22 | } 23 | 24 | func (t *Trace) AddStringAttribute(key string, value string) *Trace { 25 | t.attributes = append(t.attributes, trace.StringAttribute(key, value)) 26 | return t 27 | } 28 | func (t *Trace) AddInt64Attribute(key string, value int64) *Trace { 29 | t.attributes = append(t.attributes, trace.Int64Attribute(key, value)) 30 | return t 31 | } 32 | func (t *Trace) AddBoolAttribute(key string, value bool) *Trace { 33 | t.attributes = append(t.attributes, trace.BoolAttribute(key, value)) 34 | return t 35 | } 36 | 37 | func (t *Trace) AddAnnotation(timestamp time.Time, message string) *Trace { 38 | t.annotation = trace.Annotation{ 39 | Time: timestamp, 40 | Message: message, 41 | Attributes: nil, 42 | } 43 | return t 44 | } 45 | -------------------------------------------------------------------------------- /transport.go: -------------------------------------------------------------------------------- 1 | package kubemq 2 | 3 | import ( 4 | "context" 5 | 6 | pb "github.com/kubemq-io/protobuf/go" 7 | ) 8 | 9 | type Transport interface { 10 | Ping(ctx context.Context) (*ServerInfo, error) 11 | SendEvent(ctx context.Context, event *Event) error 12 | StreamEvents(ctx context.Context, eventsCh chan *Event, errCh chan error) 13 | SubscribeToEvents(ctx context.Context, request *EventsSubscription, errCh chan error) (<-chan *Event, error) 14 | SendEventStore(ctx context.Context, eventStore *EventStore) (*EventStoreResult, error) 15 | StreamEventsStore(ctx context.Context, eventsCh chan *EventStore, eventsResultCh chan *EventStoreResult, errCh chan error) 16 | SubscribeToEventsStore(ctx context.Context, request *EventsStoreSubscription, errCh chan error) (<-chan *EventStoreReceive, error) 17 | SendCommand(ctx context.Context, command *Command) (*CommandResponse, error) 18 | SubscribeToCommands(ctx context.Context, request *CommandsSubscription, errCh chan error) (<-chan *CommandReceive, error) 19 | SendQuery(ctx context.Context, query *Query) (*QueryResponse, error) 20 | SubscribeToQueries(ctx context.Context, request *QueriesSubscription, errCh chan error) (<-chan *QueryReceive, error) 21 | SendResponse(ctx context.Context, response *Response) error 22 | SendQueueMessage(ctx context.Context, msg *QueueMessage) (*SendQueueMessageResult, error) 23 | SendQueueMessages(ctx context.Context, msg []*QueueMessage) ([]*SendQueueMessageResult, error) 24 | ReceiveQueueMessages(ctx context.Context, req *ReceiveQueueMessagesRequest) (*ReceiveQueueMessagesResponse, error) 25 | AckAllQueueMessages(ctx context.Context, req *AckAllQueueMessagesRequest) (*AckAllQueueMessagesResponse, error) 26 | StreamQueueMessage(ctx context.Context, reqCh chan *pb.StreamQueueMessagesRequest, resCh chan *pb.StreamQueueMessagesResponse, errCh chan error, doneCh chan bool) 27 | QueuesInfo(ctx context.Context, filter string) (*QueuesInfo, error) 28 | GetGRPCRawClient() (pb.KubemqClient, error) 29 | Close() error 30 | } 31 | --------------------------------------------------------------------------------