fmq 0.3.0 → 0.3.1
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.
- data/History.txt +11 -1
- data/README.txt +1 -1
- data/default-server/config.ru +14 -16
- data/default-server/queues/my_test.rb +3 -3
- data/lib/fmq/admin.rb +64 -51
- data/lib/fmq/queue_manager.rb +56 -136
- data/lib/fmq/queues/README.txt +3 -3
- data/lib/fmq/queues/base.rb +72 -8
- data/lib/fmq/queues/linked.rb +4 -4
- data/lib/fmq/queues/load_balanced.rb +10 -0
- data/lib/fmq/server.rb +17 -17
- data/lib/fmq/version.rb +1 -1
- data/test/test_basic.rb +56 -6
- data/test/test_queue_manager.rb +18 -14
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
-
== 0.3.
|
1
|
+
== 0.3.1 2008-08-27
|
2
|
+
|
3
|
+
* 2 minor changes:
|
4
|
+
* refactored the queue manager and remove legacy code (moved constraints stuff to the base queue)
|
5
|
+
* refactored the setup_queue method in the configuration file (sorry you have to change your configs)
|
6
|
+
* 3 tiny changes:
|
7
|
+
* default-server can now be used from within the development repository (no rund trip nessesary)
|
8
|
+
* added and replaced some tests for the new interface of the queue manager and the base queue
|
9
|
+
* one can now yield and return messages
|
10
|
+
|
11
|
+
== 0.3.0 2008-08-26
|
2
12
|
|
3
13
|
* 2 minor changes:
|
4
14
|
* replaced the mongrel handler by rack so that it can be used by many servers and in different configurations
|
data/README.txt
CHANGED
@@ -34,7 +34,7 @@ stores it’s internal data in an FIFO in system memory.
|
|
34
34
|
== FEATURES/PROBLEMS:
|
35
35
|
|
36
36
|
* FIFO message store
|
37
|
-
* easy setup and maintenance of system
|
37
|
+
* easy setup and maintenance of system (rack)
|
38
38
|
* using http for communication
|
39
39
|
* changeable queue implementation
|
40
40
|
* ruby client lib
|
data/default-server/config.ru
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
begin
|
2
|
+
require File.dirname(__FILE__) + '/../lib/fmq'
|
3
|
+
rescue LoadError
|
4
|
+
require "fmq"
|
5
|
+
end
|
2
6
|
|
3
7
|
# load all local queues (from project directory)
|
4
8
|
Dir.glob("queues/*.rb").each { |f| require f }
|
@@ -24,39 +28,33 @@ queue_manager.setup do |qm|
|
|
24
28
|
# define there url and constraints here
|
25
29
|
# =====================================================
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
q.path = "/fmq_test/test1"
|
31
|
+
# the path to the queue e.g. /app1/myframe/test1
|
32
|
+
# means http://localhost:5884/app1/myframe/test1
|
33
|
+
# this parameter is not optional
|
34
|
+
qm.setup_queue "/fmq_test/test1" do |q|
|
32
35
|
# this defines the maximum count of messages that
|
33
36
|
# can be in the queue, if the queue is full every
|
34
37
|
# new message will be rejected with a http error
|
35
38
|
# this parameter is optional if you don't specify
|
36
39
|
# a max value the queue size depends on your system
|
37
|
-
q.max_messages =
|
40
|
+
q.max_messages = 1_000_000
|
38
41
|
# this optional to and specifys the max content size
|
39
42
|
# for all data of a queue
|
40
43
|
# valid extensions are kb, mb, gb
|
41
|
-
q.max_size =
|
44
|
+
q.max_size = 10.kb
|
42
45
|
end
|
43
46
|
|
44
47
|
# if you want you can specify the class of the queue
|
45
48
|
# this is interessting if you write your own queues
|
46
|
-
qm.setup_queue FreeMessageQueue::LoadBalancedQueue
|
47
|
-
q.path = "/fmq_test/test2"
|
48
|
-
end
|
49
|
+
qm.setup_queue "/fmq_test/test2", FreeMessageQueue::LoadBalancedQueue
|
49
50
|
|
50
51
|
# if you have special queues include put them into the queues
|
51
52
|
# folder and and use them (this MyTestQueue is places in queues/mytest.rb)
|
52
|
-
qm.setup_queue MyTestQueue
|
53
|
-
q.path = "/fmq_test/test3"
|
54
|
-
end
|
53
|
+
qm.setup_queue "/fmq_test/test3", MyTestQueue
|
55
54
|
|
56
55
|
# this is a forwarding queue wich forwards one message
|
57
56
|
# to some other queues
|
58
|
-
qm.setup_queue FreeMessageQueue::ForwardQueue do |q|
|
59
|
-
q.path = "/fmq_test/forward_to_1_and_2"
|
57
|
+
qm.setup_queue "/fmq_test/forward_to_1_and_2", FreeMessageQueue::ForwardQueue do |q|
|
60
58
|
# you can add as may queues as you want
|
61
59
|
# but seperate them with a space char
|
62
60
|
q.forward_to = ["/fmq_test/test1", "/fmq_test/test2"]
|
@@ -4,8 +4,8 @@ class MyTestQueue < FreeMessageQueue::BaseQueue
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def poll
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
FreeMessageQueue::Message.new "Hello World", "text/plain" do |m|
|
8
|
+
m.option["Time"] = Time.now
|
9
|
+
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/fmq/admin.rb
CHANGED
@@ -25,72 +25,85 @@ end
|
|
25
25
|
|
26
26
|
module FreeMessageQueue
|
27
27
|
# This class is dedicated to the AJAX based admin interface.
|
28
|
-
class AdminInterface
|
29
|
-
# rack response for bad requests
|
30
|
-
WRONG_METHOD = [400, {"CONTENT-TYPE" => "text/plain", "ERROR" => "Wrong http Method"}, ""]
|
31
|
-
|
32
|
-
# rack response for good requests
|
33
|
-
OK = [200, {"CONTENT-TYPE" => "text/plain"}, ["ok"]]
|
34
|
-
|
35
|
-
# create the admin interface for the passed QueueManager
|
36
|
-
def initialize(queue_manager)
|
37
|
-
@manager = queue_manager
|
38
|
-
@log = FreeMessageQueue.logger
|
39
|
-
end
|
40
|
-
|
41
|
-
# handle all requests
|
42
|
-
def call(env)
|
43
|
-
begin
|
44
|
-
request = Rack::Request.new(env)
|
45
|
-
@log.debug "[AdminInterface] ENV: #{env.inspect}"
|
46
|
-
|
47
|
-
if request.get? then
|
48
|
-
@log.info "[AdminInterface] list queues"
|
28
|
+
class AdminInterface
|
29
|
+
# rack response for bad requests
|
30
|
+
WRONG_METHOD = [400, {"CONTENT-TYPE" => "text/plain", "ERROR" => "Wrong http Method"}, ""]
|
31
|
+
|
32
|
+
# rack response for good requests
|
33
|
+
OK = [200, {"CONTENT-TYPE" => "text/plain"}, ["ok"]]
|
34
|
+
|
35
|
+
# create the admin interface for the passed QueueManager
|
36
|
+
def initialize(queue_manager)
|
37
|
+
@manager = queue_manager
|
38
|
+
@log = FreeMessageQueue.logger
|
39
|
+
end
|
40
|
+
|
41
|
+
# handle all requests
|
42
|
+
def call(env)
|
43
|
+
begin
|
44
|
+
request = Rack::Request.new(env)
|
45
|
+
@log.debug "[AdminInterface] ENV: #{env.inspect}"
|
46
|
+
|
47
|
+
if request.get? then
|
48
|
+
@log.info "[AdminInterface] list queues"
|
49
49
|
# ======= LIST QUEUES
|
50
50
|
queues_code = []
|
51
51
|
@manager.queues.each do |queue_name|
|
52
52
|
queues_code << queue_to_json(queue_name)
|
53
|
-
end
|
54
|
-
|
55
|
-
return [200, {"CONTENT-TYPE" => "application/json"}, ["[%s]" % queues_code.join(","), ]]
|
56
|
-
elsif request.post? then
|
57
|
-
if request["_method"] == "delete" then
|
58
|
-
# ======= DELETE QUEUE
|
53
|
+
end
|
54
|
+
|
55
|
+
return [200, {"CONTENT-TYPE" => "application/json"}, ["[%s]" % queues_code.join(","), ]]
|
56
|
+
elsif request.post? then
|
57
|
+
if request["_method"] == "delete" then
|
58
|
+
# ======= DELETE QUEUE
|
59
59
|
@log.info "[AdminInterface] delete queue"
|
60
|
-
@manager.delete_queue(request["path"])
|
61
|
-
return OK
|
62
|
-
elsif request["_method"] == "create"
|
63
|
-
# ======= CREATE QUEUE
|
60
|
+
@manager.delete_queue(request["path"])
|
61
|
+
return OK
|
62
|
+
elsif request["_method"] == "create"
|
63
|
+
# ======= CREATE QUEUE
|
64
64
|
@log.info "[AdminInterface] create queue"
|
65
|
-
@manager.
|
66
|
-
request["max_messages"].
|
67
|
-
request["max_size"]
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
65
|
+
@manager.setup_queue request["path"] do |q|
|
66
|
+
q.max_messages = request["max_messages"].to_i
|
67
|
+
q.max_size = str_bytes request["max_size"]
|
68
|
+
end
|
69
|
+
return OK
|
70
|
+
else
|
71
|
+
return WRONG_METHOD
|
72
|
+
end
|
73
|
+
else
|
74
|
+
return WRONG_METHOD
|
75
|
+
end
|
76
|
+
rescue QueueManagerException => ex
|
77
|
+
return [400, {"CONTENT-TYPE" => "text/plain", "ERROR" => ex.message}, [ex.message]]
|
78
|
+
end
|
78
79
|
end
|
79
80
|
|
80
81
|
private
|
81
82
|
|
82
83
|
# converts the data of one queue to json format
|
83
84
|
def queue_to_json(queue_name)
|
84
|
-
|
85
|
+
queue = @manager.queue(queue_name)
|
85
86
|
|
86
87
|
"[\"%s\", %d, %d, %d, %d]" % [
|
87
88
|
queue_name,
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
89
|
+
queue.bytes,
|
90
|
+
queue.max_size,
|
91
|
+
queue.size,
|
92
|
+
queue.max_messages,
|
92
93
|
]
|
93
94
|
end
|
95
|
+
|
96
|
+
# Retuns count of bytes to a expression with kb, mb or gb
|
97
|
+
# e.g 10kb will return 10240
|
98
|
+
def str_bytes(str)
|
99
|
+
case str
|
100
|
+
when /([0-9]+)kb/i: $1.to_i.kb
|
101
|
+
when /([0-9]+)mb/i: $1.to_i.mb
|
102
|
+
when /([0-9]+)gb/i: $1.to_i.gb
|
103
|
+
else
|
104
|
+
BaseQueue::INFINITE
|
105
|
+
end
|
106
|
+
end
|
94
107
|
end
|
95
|
-
end
|
96
|
-
|
108
|
+
end
|
109
|
+
|
data/lib/fmq/queue_manager.rb
CHANGED
@@ -18,6 +18,21 @@
|
|
18
18
|
#
|
19
19
|
require "ostruct"
|
20
20
|
|
21
|
+
# add kb, mb and gb methods for easy use in the config file
|
22
|
+
class Fixnum
|
23
|
+
def kb
|
24
|
+
self * 1024
|
25
|
+
end
|
26
|
+
|
27
|
+
def mb
|
28
|
+
self.kb * 1024
|
29
|
+
end
|
30
|
+
|
31
|
+
def gb
|
32
|
+
self.mb * 1024
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
21
36
|
module FreeMessageQueue
|
22
37
|
# All queue manager exceptions are raised using this class
|
23
38
|
class QueueManagerException < Exception
|
@@ -40,23 +55,8 @@ module FreeMessageQueue
|
|
40
55
|
# corresponding constraints. Every queue that is created by this
|
41
56
|
# queue manager will get a reference (<em>manager</em>) for later use.
|
42
57
|
class QueueManager
|
43
|
-
# Returns the queue that is passed otherwise nil
|
44
|
-
attr_reader :queue
|
45
|
-
|
46
58
|
# <b>true</b> to let the queue manager create a queue automaticly
|
47
59
|
attr_writer :auto_create_queues
|
48
|
-
|
49
|
-
# Returns the queue constrains as a hash. The hash has the following structure:
|
50
|
-
# {
|
51
|
-
# :max_size => "100mb",
|
52
|
-
# :max_messages => 1000
|
53
|
-
# }
|
54
|
-
attr_reader :queue_constraints
|
55
|
-
|
56
|
-
# This value is used to decribe that a constraint has no limit e.g.
|
57
|
-
# :max_messages => INFINITE
|
58
|
-
# means that there is no limitation for messages
|
59
|
-
INFINITE = -1
|
60
60
|
|
61
61
|
# this is the default queue class if no other is specified this
|
62
62
|
# class will be created when setting up a queue
|
@@ -65,8 +65,7 @@ module FreeMessageQueue
|
|
65
65
|
# setup the queue manager using the configuration from the configuration
|
66
66
|
# file (which is basically a hash)
|
67
67
|
def initialize()
|
68
|
-
@
|
69
|
-
@queue_constraints = {}
|
68
|
+
@queues = {}
|
70
69
|
@log = FreeMessageQueue.logger
|
71
70
|
@auto_create_queues = true
|
72
71
|
end
|
@@ -76,44 +75,26 @@ module FreeMessageQueue
|
|
76
75
|
def auto_create_queues?
|
77
76
|
@auto_create_queues == true
|
78
77
|
end
|
79
|
-
|
80
|
-
# Create a queue (<em>name</em> => <em>path</em>). The path must contain a leading "/" and a 3 character name
|
81
|
-
# at minimum. Exceptions will be raised if the queue allready exists.
|
82
|
-
def create_queue(name, max_messages = INFINITE, max_size = INFINITE, default_class = DEFAULT_QUEUE_CLASS)
|
83
|
-
# path must begin with /
|
84
|
-
raise QueueManagerException.new("[QueueManager] Leading / in path '#{name}' missing", caller) if name[0..0] != "/"
|
85
|
-
|
86
|
-
# path must have a minimus lenght of 3 character
|
87
|
-
raise QueueManagerException.new("[QueueManager] The queue path '#{name}' is to short 3 character is minimum", caller) if name.size - 1 < 3
|
88
|
-
|
89
|
-
# don't create a queue twice
|
90
|
-
raise QueueManagerException.new("[QueueManager] The queue '#{name}' allready exists", caller) if queue_exists? name
|
91
|
-
|
92
|
-
@log.info("[QueueManager] Create queue '#{name}' {type: #{default_class}, max_messages: #{max_messages}, max_size: #{max_size}}")
|
93
|
-
|
94
|
-
@queue[name] = default_class.new(self)
|
95
|
-
@queue_constraints[name] = {
|
96
|
-
:max_messages => max_messages,
|
97
|
-
:max_size => max_size
|
98
|
-
}
|
99
|
-
|
100
|
-
@queue[name]
|
101
|
-
end
|
102
78
|
|
103
79
|
# create a queue using a block. The block can be used to set configuration
|
104
80
|
# options for the queue
|
105
|
-
def setup_queue(queue_class = DEFAULT_QUEUE_CLASS
|
106
|
-
|
107
|
-
|
108
|
-
|
81
|
+
def setup_queue(path, queue_class = DEFAULT_QUEUE_CLASS)
|
82
|
+
check_queue_name(path)
|
83
|
+
queue_object = queue_class.new(self)
|
84
|
+
if block_given? then
|
85
|
+
yield queue_object
|
86
|
+
end
|
87
|
+
@queues[path] = queue_object
|
88
|
+
@log.info("[QueueManager] Create queue '#{path}' {type: #{queue_class}, max_messages: #{queue_object.max_messages}, max_size: #{queue_object.max_size}}")
|
89
|
+
return queue_object
|
109
90
|
end
|
110
91
|
|
111
92
|
# Delete the queue by name (path)
|
112
93
|
def delete_queue(name)
|
113
|
-
if
|
114
|
-
@log.info("[QueueManager] Delete queue '#{name}' with #{
|
115
|
-
|
116
|
-
@
|
94
|
+
if queue_exists? name
|
95
|
+
@log.info("[QueueManager] Delete queue '#{name}' with #{queue(name).size} messages")
|
96
|
+
queue(name).clear
|
97
|
+
@queues.delete name
|
117
98
|
true
|
118
99
|
else
|
119
100
|
raise QueueManagerException.new("[QueueManager] There is no queue '#{name}'", caller)
|
@@ -122,10 +103,10 @@ module FreeMessageQueue
|
|
122
103
|
|
123
104
|
# This returns one message from the passed queue
|
124
105
|
def poll(name)
|
125
|
-
if
|
126
|
-
@log.debug("[QueueManager] Poll from queue '#{name}' with #{
|
127
|
-
if
|
128
|
-
queue_item =
|
106
|
+
if queue_exists? name
|
107
|
+
@log.debug("[QueueManager] Poll from queue '#{name}' with #{queue(name).size} messages")
|
108
|
+
if queue(name).respond_to? :poll
|
109
|
+
queue_item = queue(name).poll
|
129
110
|
else
|
130
111
|
raise QueueManagerException.new("[QueueManager] You can't poll from queue '#{name}'", caller)
|
131
112
|
end
|
@@ -141,7 +122,8 @@ module FreeMessageQueue
|
|
141
122
|
# will be generated if there isn't a queue with the passed name (path). Otherwise
|
142
123
|
# it will raise a QueueManagerException if the passed queue doesn't exists.
|
143
124
|
def put(name, message)
|
144
|
-
|
125
|
+
# check for auto createing queues if they are not available
|
126
|
+
unless queue_exists? name
|
145
127
|
# only auto create queues if it is configured
|
146
128
|
if auto_create_queues?
|
147
129
|
create_queue(name)
|
@@ -149,22 +131,10 @@ module FreeMessageQueue
|
|
149
131
|
raise QueueManagerException.new("[QueueManager] There is no queue '#{name}'", caller)
|
150
132
|
end
|
151
133
|
end
|
152
|
-
|
153
|
-
# check max size constraints
|
154
|
-
if @queue_constraints[name][:max_size] != INFINITE &&
|
155
|
-
@queue_constraints[name][:max_size] < queue[name].bytes + message.bytes
|
156
|
-
raise QueueManagerException.new("[QueueManager] The queue '#{name}' is full, max amount of space (#{@queue_constraints[name][:max_size]}) is exceeded", caller)
|
157
|
-
end
|
158
|
-
|
159
|
-
# check max messages constraints
|
160
|
-
if @queue_constraints[name][:max_messages] != INFINITE &&
|
161
|
-
@queue_constraints[name][:max_messages] < queue[name].size + 1
|
162
|
-
raise QueueManagerException.new("[QueueManager] The queue '#{name}' is full, max amount of messages (#{@queue_constraints[name][:max_messages]}) is exceeded", caller)
|
163
|
-
end
|
164
134
|
|
165
|
-
@log.debug("[QueueManager] put message to queue '#{name}' with #{
|
166
|
-
if
|
167
|
-
|
135
|
+
@log.debug("[QueueManager] put message to queue '#{name}' with #{queue(name).size} messages")
|
136
|
+
if queue(name).respond_to? :put
|
137
|
+
queue(name).put(message)
|
168
138
|
else
|
169
139
|
raise QueueManagerException.new("[QueueManager] You can't put to queue '#{name}'", caller)
|
170
140
|
end
|
@@ -174,22 +144,12 @@ module FreeMessageQueue
|
|
174
144
|
|
175
145
|
# Returns the names (paths) of all queues managed by this queue manager
|
176
146
|
def queues
|
177
|
-
@
|
178
|
-
end
|
179
|
-
|
180
|
-
# Returns the size (number of messages)
|
181
|
-
def queue_size(name)
|
182
|
-
@queue[name].size
|
183
|
-
end
|
184
|
-
|
185
|
-
# Returns the byte size of the queue
|
186
|
-
def queue_bytes(name)
|
187
|
-
@queue[name].bytes
|
147
|
+
@queues.keys
|
188
148
|
end
|
189
149
|
|
190
150
|
# Is the name (path) of the queue in use allready
|
191
151
|
def queue_exists?(name)
|
192
|
-
!queue
|
152
|
+
!queue(name).nil?
|
193
153
|
end
|
194
154
|
|
195
155
|
# setup a this queue manager block need to be passed
|
@@ -197,63 +157,23 @@ module FreeMessageQueue
|
|
197
157
|
config.call(self)
|
198
158
|
end
|
199
159
|
|
200
|
-
|
201
|
-
|
202
|
-
# create a queue from a configuration hash.
|
203
|
-
# The <em>queue_config</em> contains the following parameter:
|
204
|
-
# * path: the path to the queue (with leading "/" and 3 characters at minimum) e.g. "/test_queue"
|
205
|
-
# * [optional] max_size: the maximum size e.g. "10mb", "100kb", "2gb" or (black or -1) for infinite
|
206
|
-
# * [optional] max_messages: the maximim messages that can be in the queue e.g. 1500 or (black or -1) for infinite
|
207
|
-
# All other parameter will be send to the queue directly using a naming convention. So if you have the extra parameter
|
208
|
-
# expire_date = "1h"
|
209
|
-
# the QueueManager will set the expire date using this assignment
|
210
|
-
# queue.expire_date = "1h"
|
211
|
-
# therefore your queue must implement this method
|
212
|
-
# def expire_date=(time)
|
213
|
-
# @expires_after = parse_seconds(time)
|
214
|
-
# end
|
215
|
-
def create_queue_from_config(queue_class, queue_config)
|
216
|
-
# path need to be specified
|
217
|
-
raise QueueManagerException.new("[QueueManager] There is now path specified for queue", caller) if queue_config[:path].nil?
|
218
|
-
path = queue_config[:path]
|
219
|
-
queue_config.delete :path
|
220
|
-
|
221
|
-
@log.debug("[QueueManager] setup queue from config '#{path}'")
|
222
|
-
|
223
|
-
# set max size parameter -- this parameter is optional
|
224
|
-
max_size = str_bytes(queue_config[:max_size])
|
225
|
-
max_size = INFINITE if max_size.nil? || max_size <= 0
|
226
|
-
queue_config.delete :max_size
|
227
|
-
|
228
|
-
# set max messages parameter -- this parameter is optional
|
229
|
-
max_messages = queue_config[:max_messages].to_i
|
230
|
-
max_messages = INFINITE if max_messages.nil? || max_messages <= 0
|
231
|
-
queue_config.delete :max_messages
|
232
|
-
|
233
|
-
queue = create_queue(path, max_messages, max_size, queue_class)
|
234
|
-
|
235
|
-
if queue_config.size > 0
|
236
|
-
@log.debug("[QueueManager] Configure addional parameters for queue '#{path}'; parameter: #{queue_config.inspect}")
|
237
|
-
for method_name in queue_config.keys
|
238
|
-
queue.send(method_name.to_s + "=", queue_config[method_name])
|
239
|
-
end
|
240
|
-
end
|
160
|
+
def queue(name)
|
161
|
+
return @queues[name]
|
241
162
|
end
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
bs
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
# Create a queue (<em>name</em> => <em>path</em>). The path must contain a leading "/" and a 3 character name
|
167
|
+
# at minimum. Exceptions will be raised if the queue allready exists.
|
168
|
+
def check_queue_name(name)
|
169
|
+
# path must begin with /
|
170
|
+
raise QueueManagerException.new("[QueueManager] Leading / in path '#{name}' missing", caller) if name[0..0] != "/"
|
171
|
+
|
172
|
+
# path must have a minimus lenght of 3 character
|
173
|
+
raise QueueManagerException.new("[QueueManager] The queue path '#{name}' is to short 3 character is minimum", caller) if name.size - 1 < 3
|
174
|
+
|
175
|
+
# don't create a queue twice
|
176
|
+
raise QueueManagerException.new("[QueueManager] The queue '#{name}' allready exists", caller) if queue_exists? name
|
257
177
|
end
|
258
178
|
end
|
259
179
|
end
|
data/lib/fmq/queues/README.txt
CHANGED
@@ -20,8 +20,8 @@ First of all, this template is where you start.
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def poll
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
FreeMessageQueue::Message.new "Hello World", "text/plain" do |m|
|
24
|
+
m.option["Time"] = Time.now
|
25
|
+
end
|
26
26
|
end
|
27
27
|
end
|
data/lib/fmq/queues/base.rb
CHANGED
@@ -31,26 +31,53 @@ module FreeMessageQueue
|
|
31
31
|
@created_at = created_at
|
32
32
|
@content_type = content_type
|
33
33
|
@option = {}
|
34
|
+
if block_given? then
|
35
|
+
yield self
|
36
|
+
end
|
34
37
|
end
|
35
38
|
|
36
|
-
#
|
39
|
+
# Size of item in bytes
|
37
40
|
def bytes
|
38
41
|
@payload.size
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
45
|
+
# All queue exceptions are raised using this class
|
46
|
+
class QueueException < Exception
|
47
|
+
attr_accessor :message, :backtrace
|
48
|
+
|
49
|
+
# Create exception with message and backtrace (if needed)
|
50
|
+
def initialize(message, callstack = [])
|
51
|
+
@message = message
|
52
|
+
@backtrace = callstack
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the message of the exception
|
56
|
+
def to_s
|
57
|
+
@message
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
42
61
|
# every queue has to have this interface
|
43
62
|
class BaseQueue
|
44
63
|
# QueueManager refrence
|
45
64
|
attr_accessor :manager
|
65
|
+
|
66
|
+
# This value is used to decribe that a constraint has no limit e.g.
|
67
|
+
# max_messages = INFINITE
|
68
|
+
# means that there is no limitation for messages (by count)
|
69
|
+
INFINITE = -1
|
46
70
|
|
47
71
|
attr_reader :bytes, # the amount of space that is used by all messages in the queue
|
48
|
-
:size # the size / depp of the queue = count of messages
|
72
|
+
:size, # the size / depp of the queue = count of messages
|
73
|
+
:max_messages, # the max count of messages that can be in the queue
|
74
|
+
:max_size # the max size (bytes) of messages that can be in the queue
|
49
75
|
|
50
76
|
def initialize(manager)
|
51
77
|
@manager = manager
|
52
78
|
@bytes = 0
|
53
79
|
@size = 0
|
80
|
+
@max_size = @max_messages = INFINITE
|
54
81
|
end
|
55
82
|
|
56
83
|
# returns true if there is no message in the queue
|
@@ -58,18 +85,55 @@ module FreeMessageQueue
|
|
58
85
|
size == 0
|
59
86
|
end
|
60
87
|
|
88
|
+
# check that one can only set valid constraints
|
89
|
+
def max_messages=(val)
|
90
|
+
@max_messages = (val <= 0) ? BaseQueue::INFINITE : val
|
91
|
+
end
|
92
|
+
|
93
|
+
# check that one can only set valid constraints
|
94
|
+
def max_size=(val)
|
95
|
+
@max_size = (val <= 0) ? BaseQueue::INFINITE : val
|
96
|
+
end
|
97
|
+
|
61
98
|
protected
|
62
99
|
|
63
|
-
# update queue size and memory usage (add one
|
64
|
-
def add_message(
|
100
|
+
# update queue size and memory usage (add one message)
|
101
|
+
def add_message(message)
|
102
|
+
check_constraints_with_new(message)
|
103
|
+
|
65
104
|
@size += 1
|
66
|
-
@bytes += bytes
|
105
|
+
@bytes += message.bytes
|
106
|
+
return message
|
67
107
|
end
|
68
108
|
|
69
|
-
# update queue size and memory usage (remove one
|
70
|
-
def remove_message(
|
109
|
+
# update queue size and memory usage (remove one message)
|
110
|
+
def remove_message(message)
|
71
111
|
@size -= 1
|
72
|
-
@bytes -= bytes
|
112
|
+
@bytes -= message.bytes
|
113
|
+
return message
|
114
|
+
end
|
115
|
+
|
116
|
+
# check all constraints that are available.
|
117
|
+
# throws an exception if a constraint failed
|
118
|
+
def check_constraints_with_new(message)
|
119
|
+
check_max_size_constraint(message)
|
120
|
+
check_max_messages_constraint(message)
|
121
|
+
end
|
122
|
+
|
123
|
+
# check if max size of messages will exceed with new message.
|
124
|
+
# throws an exception if a constraint failed
|
125
|
+
def check_max_size_constraint(message)
|
126
|
+
if @max_size != INFINITE && (@max_size < self.bytes + message.bytes)
|
127
|
+
raise QueueException.new("[Queue] The queue is full, max amount of space (#{@max_size} bytes) is exceeded", caller)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# check if max count of messages will exceed with new message.
|
132
|
+
# throws an exception if a constraint failed
|
133
|
+
def check_max_messages_constraint(message)
|
134
|
+
if @max_messages != INFINITE && (@max_messages < self.size + 1)
|
135
|
+
raise QueueException.new("[Queue] The queue is full, max amount of messages (#{@max_messages}) is exceeded", caller)
|
136
|
+
end
|
73
137
|
end
|
74
138
|
end
|
75
139
|
end
|
data/lib/fmq/queues/linked.rb
CHANGED
@@ -37,6 +37,8 @@ module FreeMessageQueue
|
|
37
37
|
def put(message)
|
38
38
|
return false if message == nil
|
39
39
|
|
40
|
+
add_message(message) # update stats and check constraints
|
41
|
+
|
40
42
|
# insert at end of list
|
41
43
|
if @first_message == nil
|
42
44
|
# first and last item are same if there is no item to the queue
|
@@ -46,7 +48,7 @@ module FreeMessageQueue
|
|
46
48
|
@last_message = @last_message.next = message
|
47
49
|
end
|
48
50
|
|
49
|
-
|
51
|
+
return true
|
50
52
|
end
|
51
53
|
|
52
54
|
# Return an message from the queue or nil if the queue is empty
|
@@ -59,9 +61,7 @@ module FreeMessageQueue
|
|
59
61
|
@first_message = message.next
|
60
62
|
@last_message = nil if @first_message.nil?
|
61
63
|
message.next = nil # unlink the message
|
62
|
-
remove_message(message
|
63
|
-
|
64
|
-
return message
|
64
|
+
remove_message(message) # update stats
|
65
65
|
else
|
66
66
|
return nil
|
67
67
|
end
|
@@ -75,6 +75,16 @@ module FreeMessageQueue
|
|
75
75
|
@queues[next_put_index].put(data)
|
76
76
|
end
|
77
77
|
|
78
|
+
# queue has infinite count
|
79
|
+
def max_messages
|
80
|
+
BaseQueue::INFINITE
|
81
|
+
end
|
82
|
+
|
83
|
+
# queue has infinite messages
|
84
|
+
def max_size
|
85
|
+
BaseQueue::INFINITE
|
86
|
+
end
|
87
|
+
|
78
88
|
private
|
79
89
|
|
80
90
|
# next index acts like 'round robin'
|
data/lib/fmq/server.rb
CHANGED
@@ -34,11 +34,11 @@ module FreeMessageQueue
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# Process incoming request and send them to the right sub processing method like <em>process_get</em>
|
37
|
-
def call(env)
|
37
|
+
def call(env)
|
38
38
|
request = Rack::Request.new(env)
|
39
39
|
queue_path = request.env["REQUEST_PATH"]
|
40
40
|
@log.debug("[Server] Request params: #{YAML.dump(request.params)})")
|
41
|
-
|
41
|
+
|
42
42
|
response = nil
|
43
43
|
begin
|
44
44
|
# process supported features
|
@@ -50,9 +50,9 @@ module FreeMessageQueue
|
|
50
50
|
end
|
51
51
|
rescue QueueManagerException => ex
|
52
52
|
response = client_exception(request, queue_path, ex)
|
53
|
-
end
|
53
|
+
end
|
54
54
|
|
55
|
-
response.header["SERVER"] = SERVER_HEADER
|
55
|
+
response.header["SERVER"] = SERVER_HEADER
|
56
56
|
return response.finish
|
57
57
|
end
|
58
58
|
|
@@ -61,15 +61,15 @@ module FreeMessageQueue
|
|
61
61
|
# Returns an item from queue and sends it to the client.
|
62
62
|
# If there is no item to fetch send an 204 (NoContent) and same as HEAD
|
63
63
|
def process_get(request, queue_path)
|
64
|
-
message = @queue_manager.poll(queue_path)
|
64
|
+
message = @queue_manager.poll(queue_path)
|
65
65
|
|
66
66
|
unless message.nil? then
|
67
|
-
response = Rack::Response.new([], 200)
|
67
|
+
response = Rack::Response.new([], 200)
|
68
68
|
|
69
69
|
@log.debug("[Server] Response to GET (200)")
|
70
70
|
response.header["CONTENT-TYPE"] = message.content_type
|
71
|
-
response.header["QUEUE_SIZE"] = @queue_manager.
|
72
|
-
response.header["QUEUE_BYTES"] = @queue_manager.
|
71
|
+
response.header["QUEUE_SIZE"] = @queue_manager.queue(queue_path).size.to_s
|
72
|
+
response.header["QUEUE_BYTES"] = @queue_manager.queue(queue_path).bytes.to_s
|
73
73
|
|
74
74
|
# send all options of the message back to the client
|
75
75
|
if message.respond_to?(:option) && message.option.size > 0
|
@@ -86,13 +86,13 @@ module FreeMessageQueue
|
|
86
86
|
response = Rack::Response.new([], 204)
|
87
87
|
@log.debug("[Server] Response to GET (204)")
|
88
88
|
response.header["QUEUE_SIZE"] = response["QUEUE_BYTES"] = 0.to_s
|
89
|
-
end
|
89
|
+
end
|
90
90
|
return response
|
91
91
|
end
|
92
92
|
|
93
93
|
# Put new item to the queue and and return sam e as head action (HTTP 200)
|
94
94
|
def process_post(request, queue_path)
|
95
|
-
@log.debug("[Server] Response to POST (200)")
|
95
|
+
@log.debug("[Server] Response to POST (200)")
|
96
96
|
post_body = request.body.read
|
97
97
|
message = Message.new(post_body, request.content_type)
|
98
98
|
@log.debug("[Server] Message payload: #{post_body}")
|
@@ -107,8 +107,8 @@ module FreeMessageQueue
|
|
107
107
|
@queue_manager.put(queue_path, message)
|
108
108
|
|
109
109
|
response = Rack::Response.new([], 200)
|
110
|
-
response.header["QUEUE_SIZE"] = @queue_manager.
|
111
|
-
response.header["QUEUE_BYTES"] = @queue_manager.
|
110
|
+
response.header["QUEUE_SIZE"] = @queue_manager.queue(queue_path).size.to_s
|
111
|
+
response.header["QUEUE_BYTES"] = @queue_manager.queue(queue_path).bytes.to_s
|
112
112
|
return response
|
113
113
|
end
|
114
114
|
|
@@ -117,8 +117,8 @@ module FreeMessageQueue
|
|
117
117
|
@log.debug("[Server] Response to HEAD (200)")
|
118
118
|
|
119
119
|
response = Rack::Response.new([], 200)
|
120
|
-
response.header["QUEUE_SIZE"] = @queue_manager.
|
121
|
-
response.header["QUEUE_BYTES"] = @queue_manager.
|
120
|
+
response.header["QUEUE_SIZE"] = @queue_manager.queue(queue_path).size.to_s
|
121
|
+
response.header["QUEUE_BYTES"] = @queue_manager.queue(queue_path).bytes.to_s
|
122
122
|
return response
|
123
123
|
end
|
124
124
|
|
@@ -134,11 +134,11 @@ module FreeMessageQueue
|
|
134
134
|
# HTTP-Header field Error contains information about the problem.
|
135
135
|
# The client errorwill also be reported to warn level of logger.
|
136
136
|
def client_exception(request, queue_path, ex)
|
137
|
-
@log.warn("[Server] Client error: #{ex}")
|
137
|
+
@log.warn("[Server] Client error: #{ex}")
|
138
138
|
|
139
139
|
response = Rack::Response.new([], 400)
|
140
|
-
response.header["ERROR"] = ex.message
|
140
|
+
response.header["ERROR"] = ex.message
|
141
141
|
return response
|
142
142
|
end
|
143
143
|
end
|
144
|
-
end
|
144
|
+
end
|
data/lib/fmq/version.rb
CHANGED
data/test/test_basic.rb
CHANGED
@@ -26,11 +26,61 @@ class TestMessage < Test::Unit::TestCase
|
|
26
26
|
end
|
27
27
|
|
28
28
|
class BaseQueue < Test::Unit::TestCase
|
29
|
-
def
|
30
|
-
manager =
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
def setup
|
30
|
+
@manager = FreeMessageQueue::QueueManager.new()
|
31
|
+
|
32
|
+
@manager.setup do |m|
|
33
|
+
@queue1 = m.setup_queue "/dummy1"
|
34
|
+
@queue2 = m.setup_queue "/dummy2" do |q|
|
35
|
+
q.max_messages = 100
|
36
|
+
q.max_size = 100.kb
|
37
|
+
end
|
38
|
+
@queue3 = m.setup_queue "/dummy3" do |q|
|
39
|
+
q.max_size = 0
|
40
|
+
q.max_messages = -10
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_creating
|
46
|
+
# check first queue
|
47
|
+
assert_equal 0, @queue1.size
|
48
|
+
assert_equal 0, @queue1.bytes
|
49
|
+
assert_equal FreeMessageQueue::BaseQueue::INFINITE, @queue1.max_messages
|
50
|
+
assert_equal FreeMessageQueue::BaseQueue::INFINITE, @queue1.max_size
|
51
|
+
|
52
|
+
# check 2. queue
|
53
|
+
assert_equal 0, @queue2.size
|
54
|
+
assert_equal 0, @queue2.bytes
|
55
|
+
assert_equal 100, @queue2.max_messages
|
56
|
+
assert_equal 100 * 1024, @queue2.max_size
|
57
|
+
|
58
|
+
# check 3. queue
|
59
|
+
assert_equal 0, @queue3.size
|
60
|
+
assert_equal 0, @queue3.bytes
|
61
|
+
assert_equal FreeMessageQueue::BaseQueue::INFINITE, @queue3.max_messages
|
62
|
+
assert_equal FreeMessageQueue::BaseQueue::INFINITE, @queue3.max_size
|
63
|
+
|
64
|
+
assert_equal @manager, @queue1.manager
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_constraint_max_messages
|
68
|
+
100.times do |v|
|
69
|
+
@queue2.put(new_msg("test"))
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_raise(FreeMessageQueue::QueueException) do
|
73
|
+
@queue2.put(new_msg("test"))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_constraint_max_size
|
78
|
+
11.times do |v|
|
79
|
+
@queue2.put(new_msg("X" * 9.kb))
|
80
|
+
end
|
81
|
+
|
82
|
+
assert_raise(FreeMessageQueue::QueueException) do
|
83
|
+
@queue2.put(new_msg("X" * 10.kb))
|
84
|
+
end
|
35
85
|
end
|
36
86
|
end
|
data/test/test_queue_manager.rb
CHANGED
@@ -12,54 +12,58 @@ class TestQueueManager < Test::Unit::TestCase
|
|
12
12
|
@queue_manager.setup do |qm|
|
13
13
|
qm.auto_create_queues = false
|
14
14
|
|
15
|
-
qm.setup_queue do |q|
|
16
|
-
q.path = DEFAULT_QUEUE_NAME
|
15
|
+
qm.setup_queue DEFAULT_QUEUE_NAME do |q|
|
17
16
|
q.max_messages = 100
|
18
|
-
q.max_size =
|
17
|
+
q.max_size = 100.mb
|
19
18
|
end
|
19
|
+
|
20
|
+
qm.setup_queue "/second_test_queue"
|
21
|
+
qm.setup_queue "/third_test_queue"
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
23
25
|
def test_config
|
24
|
-
# check constraints
|
25
|
-
constraints = { :max_messages => 100, :max_size => 104857600 }
|
26
|
-
assert_equal constraints, @queue_manager.queue_constraints[DEFAULT_QUEUE_NAME]
|
27
|
-
|
28
26
|
# check that the simple config will work
|
29
27
|
FreeMessageQueue::QueueManager.new()
|
30
28
|
@queue_manager.setup do |qm|
|
31
29
|
qm.auto_create_queues = false
|
32
30
|
end
|
31
|
+
|
32
|
+
# check if all queues are available
|
33
|
+
assert_equal 3, @queue_manager.queues.size
|
34
|
+
[DEFAULT_QUEUE_NAME, "/second_test_queue", "/third_test_queue"].each do |e|
|
35
|
+
assert @queue_manager.queues.include? e
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
def test_poll_and_get
|
36
40
|
10.times do
|
37
41
|
@queue_manager.put(DEFAULT_QUEUE_NAME, new_msg("XXX" * 20))
|
38
42
|
end
|
39
|
-
assert_equal 10, @queue_manager.
|
43
|
+
assert_equal 10, @queue_manager.queue(DEFAULT_QUEUE_NAME).size
|
40
44
|
i = 0
|
41
45
|
while message = @queue_manager.poll(DEFAULT_QUEUE_NAME)
|
42
46
|
i += 1
|
43
47
|
end
|
44
48
|
assert_equal 10, i
|
45
|
-
assert_equal 0, @queue_manager.
|
49
|
+
assert_equal 0, @queue_manager.queue(DEFAULT_QUEUE_NAME).size
|
46
50
|
|
47
51
|
# should raise a exception because of the maximum messages limitation
|
48
52
|
i = 0
|
49
|
-
assert_raise(FreeMessageQueue::
|
53
|
+
assert_raise(FreeMessageQueue::QueueException) {
|
50
54
|
101.times do
|
51
55
|
@queue_manager.put(DEFAULT_QUEUE_NAME, new_msg("XXX" * 20))
|
52
56
|
i += 1
|
53
57
|
end
|
54
58
|
}
|
55
|
-
@queue_manager.queue
|
56
|
-
assert_equal 0, @queue_manager.
|
59
|
+
@queue_manager.queue(DEFAULT_QUEUE_NAME).clear
|
60
|
+
assert_equal 0, @queue_manager.queue(DEFAULT_QUEUE_NAME).size
|
57
61
|
assert i = 100
|
58
62
|
|
59
63
|
# should raise a exception because of the maximum byte size limitation
|
60
64
|
i = 0
|
61
65
|
two_mb_message = new_msg("X" * 1024 * 1024 * 2)
|
62
|
-
assert_raise(FreeMessageQueue::
|
66
|
+
assert_raise(FreeMessageQueue::QueueException) {
|
63
67
|
101.times do
|
64
68
|
@queue_manager.put(DEFAULT_QUEUE_NAME, two_mb_message)
|
65
69
|
i += 1
|
@@ -70,7 +74,7 @@ class TestQueueManager < Test::Unit::TestCase
|
|
70
74
|
|
71
75
|
def test_creating_and_deleting
|
72
76
|
url = "/XX123"
|
73
|
-
@queue_manager.
|
77
|
+
@queue_manager.setup_queue url
|
74
78
|
@queue_manager.put(url, new_msg("X_X_X_X_X_X"))
|
75
79
|
assert_equal "X_X_X_X_X_X", @queue_manager.poll(url).payload
|
76
80
|
@queue_manager.delete_queue(url)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fmq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vincent Landgraf
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-08-
|
12
|
+
date: 2008-08-27 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|