kafkr 0.9.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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