kafkr 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c15f14feed8ee398816eb4454850798f764f10cafdf12f27aca353ac2412fa1b
4
- data.tar.gz: 77c4facf9f2290d087d2b5b40aa63714e0c01f5d0f4a33cce4c85349264dfe63
3
+ metadata.gz: 41bbdf52172942afa5cb0758bee83eeb452f74f3d61849841fe186e44ef323a5
4
+ data.tar.gz: fcf59de6aed2bc3644df69573091dbffeb9641f39bb5fbdece548de3cf509217
5
5
  SHA512:
6
- metadata.gz: 3e1089f1fbe70c98be8c9ec09fe9d9967c20594c120857fa486bde44bb3ab2ef32cb02df7ae485e383ac1d802243a9468019bf9e6bfd6e057ce57fe14e465e4a
7
- data.tar.gz: e929ce1c00207dd3f63be0294f73d4b885a3786a0748e84650baedd3a01e22e9755de5acf85d111d8e0ea29ce0ba1d4e51906f5a5228c0b5b2acbb3de56857a8
6
+ metadata.gz: 1f249925d3d2de4ca159796d88628450b78fdb5d5c00c756891202d39f7d8e34b69f282613c819c2aecc734a7fc0fe0c414ea2471955c27dda394e9f27bca1d5
7
+ data.tar.gz: 8e004710cfffe4ead0df8ed181fa5f8d044ca4cb8690d6be92028473475a9b2fddea89f0c8f77fbf96ea8b3eeaa00886a8e7ec94eff338379c9797cfa8956bf7
data/.DS_Store ADDED
Binary file
data/exe/kafkr-consumer CHANGED
@@ -7,6 +7,7 @@ require "digest"
7
7
  # Accepting command line arguments for host and port
8
8
  host = ARGV[0] || "localhost"
9
9
  port = ARGV[1] || 4000
10
+ timeout = ARGV[2] || 120
10
11
 
11
12
  puts "Running on host: #{host} and port: #{port}"
12
13
 
@@ -40,6 +41,7 @@ def start_consumer(port, host)
40
41
  Kafkr::Consumer.configure do |config|
41
42
  config.port = port
42
43
  config.host = host
44
+ config.timeout = timeout
43
45
  end
44
46
 
45
47
  unless $handlers_loaded
@@ -18,6 +18,9 @@ module Kafkr
18
18
  def configuration
19
19
  FileUtils.mkdir_p "./.kafkr"
20
20
  @configuration ||= OpenStruct.new
21
+ @configuration.host = ENV.fetch("KAFKR_HOST", "localhost")
22
+ @configuration.port = ENV.fetch("KAFKR_PORT", 2000)
23
+ @configuration.timeout = ENV.fetch("KAFKR_CONSUMER_TIMEOUT", 120)
21
24
  @configuration.suggest_handlers = false
22
25
  @configuration
23
26
  end
@@ -39,7 +42,6 @@ module Kafkr
39
42
  end
40
43
 
41
44
  def load_handlers(directory = "./handlers")
42
- # Load handlers and check for new additions
43
45
  Dir.glob("#{directory}/*.rb").each do |file|
44
46
  handler_name = File.basename(file, ".rb")
45
47
  unless $loaded_handlers[handler_name]
@@ -48,11 +50,6 @@ module Kafkr
48
50
  $handlers_changed = true
49
51
  end
50
52
  end
51
-
52
- # Display handlers if there are changes
53
- if $handlers_changed
54
- $handlers_changed = false
55
- end
56
53
  end
57
54
  end
58
55
 
@@ -65,20 +62,19 @@ module Kafkr
65
62
  raise NotImplementedError, "You must implement the handle method"
66
63
  end
67
64
 
68
- # ... rest of your existing Handler methods ...
69
65
  def self.inherited(subclass)
70
66
  Consumer.register_handler(subclass.new)
71
67
  end
72
68
 
73
69
  protected
74
70
 
75
- def reply to:, payload:
71
+ def reply(to:, payload:)
76
72
  Kafkr::Producer.configure do |config|
77
73
  config.host = Consumer.configuration.host
78
74
  config.port = Consumer.configuration.port
79
75
  end
80
76
 
81
- Kafkr::Producer.send_message({reply: {payload: payload, uuid: to["sync_uid"]}}, acknowledge: false)
77
+ Kafkr::Producer.send_message({reply: {payload: payload, uuid: to["sync_uid"]}})
82
78
  end
83
79
 
84
80
  private
@@ -114,45 +110,34 @@ module Kafkr
114
110
  end
115
111
 
116
112
  def valid_class_name?(name)
117
- # A valid class name starts with an uppercase letter and
118
- # followed by zero or more letters, numbers, or underscores.
119
113
  /^[A-Z]\w*$/.match?(name)
120
114
  end
121
115
 
122
- # sugests a working handler
123
116
  def print_handler_class(name)
124
117
  return if name.is_a?(Numeric)
125
-
126
- # If name is a Hash, use its first key
127
118
  name = name.keys.first if name.is_a?(Hash)
128
-
129
- # Generate the handler name based on the naming convention
130
119
  handler_name = "#{name.downcase}_handler"
131
120
 
132
- # Check if the handler is already loaded
133
121
  if $loaded_handlers.key?(handler_name)
134
122
  return
135
123
  end
136
124
 
137
125
  if Kafkr::Consumer.configuration.suggest_handlers
138
- if valid_class_name? name.capitalize
139
- puts "No handler for this message, you could use this one."
140
- puts ""
126
+ if valid_class_name?(name.capitalize)
127
+ puts "No handler for this message, you could use this one.\n\n"
141
128
 
142
129
  handler_class_string = <<~HANDLER_CLASS
143
-
144
130
  class #{name.capitalize}Handler < Kafkr::Consumer::Handler
145
131
  def handle?(message)
146
132
  can_handle? message, '#{name}'
147
133
  end
148
-
134
+
149
135
  def handle(message)
150
136
  puts message
151
137
  end
152
138
  end
153
139
 
154
- save the file to ./handlers/#{name}_handler.rb
155
-
140
+ # Save the file to ./handlers/#{name}_handler.rb
156
141
  HANDLER_CLASS
157
142
 
158
143
  puts handler_class_string
@@ -165,38 +150,28 @@ module Kafkr
165
150
  def listen_for(message, send_message)
166
151
  attempt = 0
167
152
  begin
168
- socket = TCPSocket.new(Consumer.configuration.host, Consumer.configuration.port)
153
+ socket = TCPSocket.new(@host, @port)
169
154
  attempt = 0
170
155
 
171
- Timeout.timeout(20) do
172
- # Call the provided send_message method or lambda, passing the message as an argument
173
- sync_uid = send_message.call(message, acknowledge: false)
156
+ Timeout.timeout(Kafkr::Consumer.configuration.timeout) do
157
+ sync_uid = send_message.call(message)
174
158
 
175
159
  loop do
176
160
  received_message = socket.gets
177
161
  raise LostConnection if received_message.nil?
178
- # Assuming Kafkr::Encryptor is defined elsewhere
179
162
  received_message = Kafkr::Encryptor.new.decrypt(received_message.chomp)
180
- # Yield every received message to the given block
181
163
  if valid_json?(received_message)
182
164
  payload = yield JSON.parse(received_message), sync_uid if block_given?
183
165
  return payload if payload
184
166
  end
185
167
  end
186
168
  end
187
- rescue Timeout::Error
188
- puts "Listening timed out after 20 seconds."
189
- socket&.close
190
- rescue LostConnection
191
- attempt += 1
192
- wait_time = backoff_time(attempt)
193
- puts "Connection lost. Reconnecting in #{wait_time} seconds..."
194
- sleep(wait_time)
195
- rescue Errno::ECONNREFUSED, Timeout::Error
169
+ rescue Timeout::Error, LostConnection, Errno::ECONNREFUSED
196
170
  attempt += 1
197
171
  wait_time = backoff_time(attempt)
198
- puts "Failed to connect on attempt #{attempt}. Retrying in #{wait_time} seconds..."
172
+ puts "Attempt #{attempt}: Retrying in #{wait_time} seconds..."
199
173
  sleep(wait_time)
174
+ retry
200
175
  rescue Interrupt
201
176
  puts "Received interrupt signal. Shutting down consumer gracefully..."
202
177
  socket&.close
@@ -207,39 +182,9 @@ module Kafkr
207
182
  def listen
208
183
  attempt = 0
209
184
  loop do
210
- socket = TCPSocket.new(Consumer.configuration.host, Consumer.configuration.port)
211
- attempt = 0
212
-
213
- loop do
214
- message = socket.gets
215
- raise LostConnection if message.nil?
216
-
217
- # Assuming Kafkr::Encryptor is defined elsewhere
218
- message = Kafkr::Encryptor.new.decrypt(message.chomp)
219
- if valid_json?(message)
220
- dispatch_to_handlers(JSON.parse(message)) do |message|
221
- yield message if block_given?
222
- end
223
- else
224
- dispatch_to_handlers(message) do |message|
225
- yield message if block_given?
226
- end
227
- end
185
+ listen_for("dummy", ->(msg) { puts "Listening..." }) do |message|
186
+ puts "Received message: #{message}"
228
187
  end
229
- rescue LostConnection
230
- attempt += 1
231
- wait_time = backoff_time(attempt)
232
- puts "Connection lost. Reconnecting in #{wait_time} seconds..."
233
- sleep(wait_time)
234
- rescue Errno::ECONNREFUSED, Timeout::Error
235
- attempt += 1
236
- wait_time = backoff_time(attempt)
237
- puts "Failed to connect on attempt #{attempt}. Retrying in #{wait_time} seconds..."
238
- sleep(wait_time)
239
- rescue Interrupt
240
- puts "Received interrupt signal. Shutting down consumer gracefully..."
241
- socket&.close
242
- exit(0)
243
188
  end
244
189
  end
245
190
 
@@ -273,6 +218,5 @@ module Kafkr
273
218
  end
274
219
  end
275
220
 
276
- # Assuming the handlers directory is the default location
277
221
  Consumer.load_handlers
278
222
  end
@@ -6,7 +6,7 @@ module Kafkr
6
6
  ALGORITHM = "aes-256-cbc"
7
7
 
8
8
  def initialize
9
- @key = Base64.decode64("2wZ85yxQe0lmiQ5nsqdmPWoGB0W6HZW8S/UXVTLQ6WY=")
9
+ @key = Base64.decode64(ENV["KAFKR_ENCRYPTION_KEY"])
10
10
  end
11
11
 
12
12
  def encrypt(data)
data/lib/kafkr/log.rb CHANGED
@@ -8,22 +8,17 @@ module Kafkr
8
8
  @received_file = "./.kafkr/log.txt"
9
9
  @broker = MessageBroker.new
10
10
  @whitelist = load_whitelist
11
- @acknowledged_message_ids = load_acknowledged_message_ids
12
11
  end
13
12
 
14
- def load_acknowledged_message_ids
15
- unless File.exist?("./.kafkr/acknowledged_message_ids.txt")
16
- `mkdir -p ./.kafkr`
17
- `touch ./.kafkr/acknowledged_message_ids.txt`
13
+ def load_whitelist
14
+ whitelist = ["localhost", "::1", "127.0.0.1"]
15
+ if File.exist?("whitelist.txt")
16
+ File.readlines("whitelist.txt").each do |line|
17
+ ip = line.strip.sub(/^::ffff:/, "")
18
+ whitelist << ip
19
+ end
18
20
  end
19
-
20
- config_path = File.expand_path("./.kafkr/acknowledged_message_ids.txt")
21
- return [] unless File.exist?(config_path)
22
-
23
- File.readlines(config_path).map(&:strip)
24
- rescue Errno::ENOENT, Errno::EACCES => e
25
- puts "Error loading acknowledged_message_ids: #{e.message}"
26
- []
21
+ whitelist
27
22
  end
28
23
 
29
24
  def start
@@ -53,14 +48,7 @@ module Kafkr
53
48
  message = decryptor.decrypt(encrypted_message.chomp) # Decrypt the message here
54
49
  uuid, message_content = extract_uuid(message)
55
50
  if uuid && message_content
56
- if @acknowledged_message_ids.include?(uuid)
57
- acknowledge_existing_message(uuid, client)
58
- else
59
- acknowledge_message(uuid, client)
60
- persist_received_message(uuid)
61
- @acknowledged_message_ids << uuid
62
- @broker.broadcast(message_content)
63
- end
51
+ @broker.broadcast(message_content)
64
52
  else
65
53
  puts "Received invalid message format: #{message}"
66
54
  end
@@ -73,17 +61,6 @@ module Kafkr
73
61
  end
74
62
  end
75
63
 
76
- def load_whitelist
77
- whitelist = ["localhost", "::1", "127.0.0.1"]
78
- if File.exist?("whitelist.txt")
79
- File.readlines("whitelist.txt").each do |line|
80
- ip = line.strip.sub(/^::ffff:/, "")
81
- whitelist << ip
82
- end
83
- end
84
- whitelist
85
- end
86
-
87
64
  def whitelisted?(ip)
88
65
  @whitelist.include?(ip.gsub("::ffff:", ""))
89
66
  end
@@ -91,39 +68,15 @@ module Kafkr
91
68
  private
92
69
 
93
70
  def extract_uuid(message)
94
-
95
- #check if message if valid json
71
+ # Check if message is valid JSON
96
72
  begin
97
73
  message = JSON.parse(message)
98
-
99
74
  return message["uuid"], message
100
-
101
75
  rescue JSON::ParserError => e
102
76
  puts "Received invalid message format: #{message}"
103
77
  match_data = /^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}): (.+)$/.match(message)
104
78
  match_data ? [match_data[1], match_data[2]] : [nil, nil]
105
79
  end
106
-
107
- end
108
-
109
- def acknowledge_message(uuid, client)
110
- puts "Received message with UUID #{uuid}. Acknowledged."
111
- acknowledgment_message = "ACK: #{uuid}"
112
- client.puts(acknowledgment_message)
113
- puts "Acknowledgment sent to producer: #{acknowledgment_message}"
114
- end
115
-
116
- def acknowledge_existing_message(uuid, client)
117
- puts "Received duplicate message with UUID #{uuid}. Already Acknowledged."
118
- acknowledgment_message = "ACK-DUPLICATE: #{uuid}"
119
- client.puts(acknowledgment_message)
120
- puts "Duplicate acknowledgment sent to producer: #{acknowledgment_message}"
121
- end
122
-
123
- def persist_received_message(uuid)
124
- File.open("./.kafkr/acknowledged_message_ids.txt", "a") do |file|
125
- file.puts(uuid)
126
- end
127
80
  end
128
81
  end
129
82
  end
@@ -10,16 +10,12 @@ module Kafkr
10
10
  @@file_mutex = Mutex.new
11
11
 
12
12
  MESSAGE_QUEUE = "./.kafkr/message_queue.txt"
13
- ACKNOWLEDGED_MESSAGE_QUEUE = "./.kafkr/acknowledged_messages.txt"
14
13
 
15
14
  def self.configuration
16
15
  FileUtils.mkdir_p "./.kafkr"
17
16
  @configuration ||= OpenStruct.new
18
17
  @configuration.queue_file = MESSAGE_QUEUE
19
- @configuration.acknowledged_file = ACKNOWLEDGED_MESSAGE_QUEUE
20
18
  @configuration.message_queue = []
21
- @configuration.acknowledged_messages = []
22
- @configuration.acknowledged_messages = load_acknowledged_messages
23
19
  load_queue_from_file
24
20
  @configuration.is_json = false
25
21
  @configuration
@@ -32,48 +28,29 @@ module Kafkr
32
28
  end
33
29
 
34
30
  def self.structured_data_to_hash(input:, sync_uid:)
35
- # Check the overall structure with regex and make quotes optional
36
31
  unless /\A\w+\s*(=>|<=>)\s*((\w+:\s*['"]?[^'",]*['"]?,\s*)*(\w+:\s*['"]?[^'",]*['"]?)\s*)\z/.match?(input)
37
32
  return input
38
33
  end
39
34
 
40
35
  if input.include?("<=>")
41
- # puts "sync message"
42
- # Extract the type and key-value pairs
43
36
  type, key_values_str = input.split("<=>").map(&:strip)
44
-
45
- # puts type
46
- # puts key_values_str
47
-
48
37
  key_values = key_values_str.scan(/(\w+):\s*['"]?([^'",]*)['"]?/)
49
-
50
- # Convert the array of pairs into a hash, stripping quotes if they exist
51
38
  hash_body = key_values.to_h do |key, value|
52
39
  [key.to_sym, value.strip.gsub(/\A['"]|['"]\z/, "")]
53
40
  end
54
-
55
- # Return the final hash with the type as the key
56
41
  {type.to_sym => hash_body, :sync => true, :sync_uid => sync_uid}
57
-
58
42
  else
59
- # puts "async message"
60
- # Extract the type and key-value pairs
61
43
  type, key_values_str = input.split("=>").map(&:strip)
62
44
  key_values = key_values_str.scan(/(\w+):\s*['"]?([^'",]*)['"]?/)
63
-
64
- # Convert the array of pairs into a hash, stripping quotes if they exist
65
45
  hash_body = key_values.to_h do |key, value|
66
46
  [key.to_sym, value.strip.gsub(/\A['"]|['"]\z/, "")]
67
47
  end
68
-
69
- # Return the final hash with the type as the key
70
48
  {type.to_sym => hash_body}
71
49
  end
72
50
  end
73
51
 
74
- def self.send_message(message, acknowledge: true)
52
+ def self.send_message(message)
75
53
  uuid = SecureRandom.uuid
76
-
77
54
  message_with_uuid = nil
78
55
 
79
56
  if Kafkr::Producer.configuration.is_json
@@ -91,28 +68,14 @@ module Kafkr
91
68
  end
92
69
  end
93
70
 
94
- # Encrypt the message here
95
71
  encrypted_message_with_uuid = Kafkr::Encryptor.new.encrypt(message_with_uuid)
96
72
 
97
73
  begin
98
- if acknowledge
99
- if !@configuration.acknowledged_messages.include?(uuid)
100
- socket = TCPSocket.new(@configuration.host, @configuration.port)
101
- listen_for_acknowledgments(socket) if acknowledge
102
- send_queued_messages(socket)
103
- # Send the encrypted message instead of the plain one
104
- socket.puts(encrypted_message_with_uuid)
105
- else
106
- puts "Message with UUID #{uuid} has already been acknowledged. Skipping."
107
- end
108
- else
109
- socket = TCPSocket.new(@configuration.host, @configuration.port)
110
- send_queued_messages(socket)
111
- socket.puts(encrypted_message_with_uuid)
112
- end
74
+ socket = TCPSocket.new(@configuration.host, @configuration.port)
75
+ send_queued_messages(socket)
76
+ socket.puts(encrypted_message_with_uuid)
113
77
  rescue Errno::ECONNREFUSED
114
78
  puts "Connection refused. Queuing message: #{encrypted_message_with_uuid}"
115
- # Queue the encrypted message
116
79
  @configuration.message_queue.push(encrypted_message_with_uuid)
117
80
  save_queue_to_file
118
81
  rescue Errno::EPIPE
@@ -124,36 +87,15 @@ module Kafkr
124
87
  end
125
88
 
126
89
  def self.send_message_and_wait(message)
127
- # Using method(:send_message) to pass the send_message method as a callable object
128
-
129
90
  Consumer.new.listen_for(message, method(:send_message)) do |received_message, sync_uid|
130
- if received_message.key? "reply"
131
- if received_message["reply"].dig("uuid") == sync_uid
132
- received_message["reply"].dig("payload")
133
- end
91
+ if received_message.key? "reply" and received_message["reply"].dig("uuid") == sync_uid
92
+ received_message["reply"].dig("payload")
134
93
  end
135
94
  end
136
95
  end
137
96
 
138
97
  private
139
98
 
140
- def self.listen_for_acknowledgments(socket)
141
- Thread.new do
142
- while line = socket.gets
143
- line = line.chomp
144
- if line.start_with?("ACK:")
145
- uuid = line.split(" ")[1]
146
- handle_acknowledgment(uuid)
147
- end
148
- end
149
- end
150
- end
151
-
152
- def self.handle_acknowledgment(uuid)
153
- @configuration.acknowledged_messages << uuid
154
- save_acknowledged_messages
155
- end
156
-
157
99
  def self.retry_connection(message_with_uuid)
158
100
  sleep(5)
159
101
  send_message(message_with_uuid)
@@ -182,24 +124,6 @@ module Kafkr
182
124
  end
183
125
  end
184
126
 
185
- def self.load_acknowledged_messages
186
- @@file_mutex.synchronize do
187
- if File.exist?(@configuration.acknowledged_file)
188
- File.readlines(@configuration.acknowledged_file).map(&:chomp)
189
- else
190
- []
191
- end
192
- end
193
- end
194
-
195
- def self.save_acknowledged_messages
196
- @@file_mutex.synchronize do
197
- File.open(@configuration.acknowledged_file, "w") do |file|
198
- file.puts(@configuration.acknowledged_messages)
199
- end
200
- end
201
- end
202
-
203
127
  def self.logger
204
128
  @logger ||= Logger.new(STDOUT)
205
129
  end
data/lib/kafkr/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kafkr
4
- VERSION = "0.9.1"
4
+ VERSION = "0.11.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kafkr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delaney Kuldvee Burke
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-25 00:00:00.000000000 Z
11
+ date: 2024-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gibberish
@@ -35,6 +35,7 @@ executables:
35
35
  extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
+ - ".DS_Store"
38
39
  - ".rspec"
39
40
  - ".standard.yml"
40
41
  - README.md
@@ -71,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
72
  - !ruby/object:Gem::Version
72
73
  version: '0'
73
74
  requirements: []
74
- rubygems_version: 3.5.3
75
+ rubygems_version: 3.5.5
75
76
  signing_key:
76
77
  specification_version: 4
77
78
  summary: A homage to kafkr implmented in ruby