fmq 0.1.1 → 0.2.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.
- data/History.txt +24 -0
- data/License.txt +675 -0
- data/PostInstall.txt +3 -0
- data/README.txt +69 -0
- data/config/hoe.rb +73 -0
- data/config/requirements.rb +15 -0
- data/default-server/admin-interface/index.html +7 -7
- data/default-server/queues/my_test.rb +6 -15
- data/lib/fmq/client.rb +52 -19
- data/lib/fmq/mongrel_server.rb +43 -24
- data/lib/fmq/queue_manager.rb +265 -260
- data/lib/fmq/queues/README.txt +12 -21
- data/lib/fmq/queues/admin.rb +11 -22
- data/lib/fmq/queues/base.rb +75 -0
- data/lib/fmq/queues/file.rb +6 -19
- data/lib/fmq/queues/forward.rb +7 -12
- data/lib/fmq/queues/linked.rb +28 -59
- data/lib/fmq/queues/load_balanced.rb +105 -104
- data/lib/fmq/queues/syncronized.rb +54 -55
- data/lib/fmq/version.rb +2 -2
- data/test/test_basic.rb +36 -0
- data/test/test_fmq_client.rb +33 -26
- data/test/test_helper.rb +5 -1
- data/test/test_linked.rb +65 -0
- data/test/test_queue_manager.rb +90 -0
- metadata +22 -5
- data/setup.rb +0 -1585
- data/test/test_fmq_queue.rb +0 -47
data/README.txt
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
= Free Message Queue (FMQ)
|
2
|
+
|
3
|
+
Project website: http://fmq.rubyforge.org/
|
4
|
+
|
5
|
+
Project github repositiory: git://github.com/threez/fmq.git
|
6
|
+
|
7
|
+
== TODO:
|
8
|
+
|
9
|
+
* create full rdoc
|
10
|
+
* support of logging to file
|
11
|
+
* add client apis for other languages
|
12
|
+
* complete unit tests
|
13
|
+
|
14
|
+
== DESCRIPTION:
|
15
|
+
|
16
|
+
The project implements a queue system with a server and some client apis.
|
17
|
+
|
18
|
+
The server is a mongrel web server that holds REST-named queues.
|
19
|
+
You can GET, POST, DELETE, HEAD queue messages using the normal HTTP requests.
|
20
|
+
The system itself uses a configuration file (YAML) to setup queues at
|
21
|
+
startup or even at runtime. The queue implementations can be changed using
|
22
|
+
an easy plugin system right from your project directory.
|
23
|
+
|
24
|
+
For an simple administration and try out of the system, FMQ has an integrated ajax based web interface.
|
25
|
+
|
26
|
+
The client apis are implemented using the HTTP protocol, so that
|
27
|
+
you can use even curl to receive messages. Ruby is implemented right now, other languages will follow.
|
28
|
+
|
29
|
+
The queue itself is an URL like http://localhost:5884/myQueueName/
|
30
|
+
or http://localhost:5884/myApplication/myQueueName/. If you do a GET request to
|
31
|
+
this url with a web browser you will receive one message from the queue. The queue
|
32
|
+
stores it’s internal data in an FIFO in system memory.
|
33
|
+
|
34
|
+
== FEATURES/PROBLEMS:
|
35
|
+
|
36
|
+
* FIFO message store
|
37
|
+
* easy setup and maintainance of system
|
38
|
+
* using http for communication
|
39
|
+
* changeable queue implementation
|
40
|
+
* ruby client lib
|
41
|
+
* simple ajax admin interface
|
42
|
+
|
43
|
+
== SYNOPSIS:
|
44
|
+
|
45
|
+
After installing the gem you can start by creating a project:
|
46
|
+
|
47
|
+
fmq create my_project_name
|
48
|
+
next step is to change to the folder and start the FMQ server:
|
49
|
+
|
50
|
+
cd my_project_name
|
51
|
+
fmq
|
52
|
+
|
53
|
+
The server will start and host a admin interface on http://localhost:5884/admin/index.
|
54
|
+
|
55
|
+
== REQUIREMENTS:
|
56
|
+
|
57
|
+
* mongrel (as webserver)
|
58
|
+
|
59
|
+
== INSTALL:
|
60
|
+
|
61
|
+
Just install the gem as you expect:
|
62
|
+
|
63
|
+
sudo gem install fmq
|
64
|
+
|
65
|
+
== LICENSE:
|
66
|
+
|
67
|
+
(GNU GENERAL PUBLIC LICENSE, Version 3)
|
68
|
+
|
69
|
+
Copyright (c) 2008 Vincent Landgraf
|
data/config/hoe.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'fmq/version'
|
2
|
+
|
3
|
+
AUTHOR = 'Vincent Landgraf' # can also be an array of Authors
|
4
|
+
EMAIL = "fmq-3z@gmx.net"
|
5
|
+
DESCRIPTION = "The project implements a queue system with a server and some client apis. This project wants to be a fast and lightweight implementation with most of the features of MQS or ActiveMQ."
|
6
|
+
GEM_NAME = 'fmq' # what ppl will type to install your gem
|
7
|
+
RUBYFORGE_PROJECT = 'fmq' # The unix name for your project
|
8
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
9
|
+
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
10
|
+
EXTRA_DEPENDENCIES = [
|
11
|
+
# ['activesupport', '>= 1.3.1']
|
12
|
+
] # An array of rubygem dependencies [name, version]
|
13
|
+
|
14
|
+
@config_file = "~/.rubyforge/user-config.yml"
|
15
|
+
@config = nil
|
16
|
+
RUBYFORGE_USERNAME = "threez"
|
17
|
+
def rubyforge_username
|
18
|
+
unless @config
|
19
|
+
begin
|
20
|
+
@config = YAML.load(File.read(File.expand_path(@config_file)))
|
21
|
+
rescue
|
22
|
+
puts <<-EOS
|
23
|
+
ERROR: No rubyforge config file found: #{@config_file}
|
24
|
+
Run 'rubyforge setup' to prepare your env for access to Rubyforge
|
25
|
+
- See http://newgem.rubyforge.org/rubyforge.html for more details
|
26
|
+
EOS
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
end
|
30
|
+
RUBYFORGE_USERNAME.replace @config["username"]
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
REV = nil
|
35
|
+
# UNCOMMENT IF REQUIRED:
|
36
|
+
# REV = YAML.load(`svn info`)['Revision']
|
37
|
+
VERS = FreeMessageQueue::VERSION::STRING + (REV ? ".#{REV}" : "")
|
38
|
+
RDOC_OPTS = ['--quiet', '--title', 'fmq documentation',
|
39
|
+
"--opname", "index.html",
|
40
|
+
"--line-numbers",
|
41
|
+
"--main", "README",
|
42
|
+
"--inline-source"]
|
43
|
+
|
44
|
+
class Hoe
|
45
|
+
def extra_deps
|
46
|
+
@extra_deps.reject! { |x| Array(x).first == 'hoe' }
|
47
|
+
@extra_deps
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Generate all the Rake tasks
|
52
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
53
|
+
$hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
54
|
+
p.developer(AUTHOR, EMAIL)
|
55
|
+
p.description = DESCRIPTION
|
56
|
+
p.summary = DESCRIPTION
|
57
|
+
p.url = HOMEPATH
|
58
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
59
|
+
p.test_globs = ["test/**/test_*.rb"]
|
60
|
+
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
|
61
|
+
|
62
|
+
# == Optional
|
63
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
64
|
+
#p.extra_deps = EXTRA_DEPENDENCIES
|
65
|
+
|
66
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
67
|
+
end
|
68
|
+
|
69
|
+
CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
|
70
|
+
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
71
|
+
$hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
|
72
|
+
$hoe.rsync_args = '-av --delete --ignore-errors'
|
73
|
+
$hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
include FileUtils
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
%w[rake hoe newgem rubigen].each do |req_gem|
|
6
|
+
begin
|
7
|
+
require req_gem
|
8
|
+
rescue LoadError
|
9
|
+
puts "This Rakefile requires the '#{req_gem}' RubyGem."
|
10
|
+
puts "Installation: gem install #{req_gem} -y"
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
|
@@ -19,7 +19,7 @@
|
|
19
19
|
fillSelectWithQueuePaths("select-message-send", queues);
|
20
20
|
fillTableSpaceWithQueues("queue-table-space", queues);
|
21
21
|
},
|
22
|
-
onFailure: function(transport){ alert(transport.getHeader("
|
22
|
+
onFailure: function(transport){ alert(transport.getHeader("ERROR")) }
|
23
23
|
});
|
24
24
|
}
|
25
25
|
|
@@ -30,7 +30,7 @@
|
|
30
30
|
queues = transport.responseText.evalJSON();
|
31
31
|
fillTableSpaceWithQueues("queue-table-space", queues);
|
32
32
|
},
|
33
|
-
onFailure: function(transport){ alert(transport.getHeader("
|
33
|
+
onFailure: function(transport){ alert(transport.getHeader("ERROR")) }
|
34
34
|
});
|
35
35
|
}
|
36
36
|
|
@@ -78,7 +78,7 @@
|
|
78
78
|
toggle_element(button_id);
|
79
79
|
},
|
80
80
|
onFailure: function(transport){
|
81
|
-
alert(transport.getHeader("
|
81
|
+
alert(transport.getHeader("ERROR"));
|
82
82
|
toggle_element(button_id);
|
83
83
|
}
|
84
84
|
});
|
@@ -95,8 +95,8 @@
|
|
95
95
|
updateTable();
|
96
96
|
toggle_element(button_id);
|
97
97
|
},
|
98
|
-
onFailure: function(transport){
|
99
|
-
alert(transport.getHeader("
|
98
|
+
onFailure: function(transport) {
|
99
|
+
alert(transport.getHeader("ERROR"));
|
100
100
|
toggle_element(button_id);
|
101
101
|
}
|
102
102
|
});
|
@@ -117,7 +117,7 @@
|
|
117
117
|
updateTable();
|
118
118
|
alert("Queue /" + $("queue-create-path").value + " created successfully");
|
119
119
|
},
|
120
|
-
onFailure: function(transport){ alert(transport.getHeader("
|
120
|
+
onFailure: function(transport){ alert(transport.getHeader("ERROR")) }
|
121
121
|
});
|
122
122
|
}
|
123
123
|
|
@@ -130,7 +130,7 @@
|
|
130
130
|
updateTable();
|
131
131
|
alert("Queue " + path + " successfully deleted");
|
132
132
|
},
|
133
|
-
onFailure: function(transport){ alert(transport.getHeader("
|
133
|
+
onFailure: function(transport){ alert(transport.getHeader("ERROR")) }
|
134
134
|
});
|
135
135
|
}
|
136
136
|
</script>
|
@@ -1,20 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
attr_accessor :manager
|
5
|
-
attr_reader :bytes, :size
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@bytes = @size = 1
|
9
|
-
end
|
10
|
-
|
11
|
-
def put(data)
|
12
|
-
puts "NEW MESSAGE"
|
1
|
+
class MyTestQueue < FreeMessageQueue::BaseQueue
|
2
|
+
def put(message)
|
3
|
+
puts "INCOMMING: #{message.payload}"
|
13
4
|
end
|
14
5
|
|
15
6
|
def poll
|
16
|
-
|
17
|
-
|
18
|
-
|
7
|
+
msg = FreeMessageQueue::Message.new "Hello World", "text/plain"
|
8
|
+
msg.option["Time"] = Time.now
|
9
|
+
msg
|
19
10
|
end
|
20
11
|
end
|
data/lib/fmq/client.rb
CHANGED
@@ -27,50 +27,83 @@ module FreeMessageQueue
|
|
27
27
|
# require "fmq"
|
28
28
|
#
|
29
29
|
# # queue adress
|
30
|
-
# QA = "http://localhost/webserver_agent/
|
31
|
-
#
|
32
|
-
# my_remote_queue = FreeMessageQueue::ClientQueue.new(QA)
|
30
|
+
# QA = "http://localhost/webserver_agent/messages"
|
31
|
+
# queue = FreeMessageQueue::ClientQueue.new(QA)
|
33
32
|
#
|
34
33
|
# # pick one message
|
35
|
-
#
|
36
|
-
# puts " == URGENT MESSSAGE == "
|
37
|
-
# puts
|
34
|
+
# message = queue.poll()
|
35
|
+
# puts " == URGENT MESSSAGE == " if message.option["Priority"] == "high"
|
36
|
+
# puts message.payload
|
38
37
|
#
|
39
38
|
# # put an urgent message on the queue e.g.in yaml
|
40
|
-
#
|
39
|
+
# payload = "message:
|
41
40
|
# title: server don't answer a ping request
|
42
41
|
# date_time: 2008-06-01 20:19:28
|
43
42
|
# server: 10.10.30.62
|
44
43
|
# "
|
45
44
|
#
|
46
|
-
#
|
45
|
+
# message = FreeMessageQueue::Message.new(payload, "application/yaml")
|
46
|
+
# queue.put(message)
|
47
47
|
#
|
48
48
|
class ClientQueue
|
49
49
|
# create a connection to a queue (by url)
|
50
50
|
def initialize(url)
|
51
|
-
@url = url
|
51
|
+
@url = URI.parse(url)
|
52
52
|
end
|
53
53
|
|
54
54
|
# this returns one message from the queue as a string
|
55
55
|
def poll()
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
res = Net::HTTP.start(@url.host, @url.port) do |http|
|
57
|
+
http.get(@url.path)
|
58
|
+
end
|
59
|
+
|
60
|
+
message = Message.new(res.body, res["CONTENT-TYPE"])
|
61
|
+
|
62
|
+
res.each_key do |option_name|
|
63
|
+
if option_name.upcase.match(/MESSAGE_([a-zA-Z][a-zA-Z0-9_\-]*)/)
|
64
|
+
message.option[$1] = res[option_name]
|
65
|
+
end
|
60
66
|
end
|
61
|
-
|
67
|
+
|
68
|
+
return message
|
62
69
|
end
|
63
70
|
|
64
71
|
alias get poll
|
65
72
|
|
66
|
-
# this puts one message to the queue
|
67
|
-
def put(
|
68
|
-
|
69
|
-
|
70
|
-
|
73
|
+
# this puts one message to the queue
|
74
|
+
def put(message)
|
75
|
+
header = {}
|
76
|
+
header["CONTENT-TYPE"] = message.content_type
|
77
|
+
|
78
|
+
# send all options of the message back to the client
|
79
|
+
if message.respond_to?(:option) && message.option.size > 0
|
80
|
+
for option_name in message.option.keys
|
81
|
+
header["MESSAGE_#{option_name}"] = message.option[option_name].to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Net::HTTP.start(@url.host, @url.port) do |http|
|
86
|
+
http.post(@url.path, message.payload, header)
|
71
87
|
end
|
72
88
|
end
|
73
89
|
|
74
90
|
alias post put
|
91
|
+
|
92
|
+
# return the size (number of messages) of the queue
|
93
|
+
def size
|
94
|
+
head["QUEUE_SIZE"].to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
# return the size of the queue in bytes
|
98
|
+
def bytes
|
99
|
+
head["QUEUE_BYTES"].to_i
|
100
|
+
end
|
101
|
+
protected
|
102
|
+
# do a head request to get the state of the queue
|
103
|
+
def head()
|
104
|
+
res = Net::HTTP.start(@url.host, @url.port) do |http|
|
105
|
+
http.head(@url.path)
|
106
|
+
end
|
107
|
+
end
|
75
108
|
end
|
76
109
|
end
|
data/lib/fmq/mongrel_server.rb
CHANGED
@@ -40,7 +40,7 @@ module FreeMessageQueue
|
|
40
40
|
queue_path = request.params["REQUEST_PATH"]
|
41
41
|
method = request.params["REQUEST_METHOD"]
|
42
42
|
@log.debug("[MongrelHandler] Incomming request for #{queue_path} [#{method}] (#{request.params["REMOTE_ADDR"]})")
|
43
|
-
@log.debug("[MongrelHandler] Request
|
43
|
+
@log.debug("[MongrelHandler] Request params: #{YAML.dump(request.params)})")
|
44
44
|
|
45
45
|
begin
|
46
46
|
# process supported features
|
@@ -62,24 +62,33 @@ module FreeMessageQueue
|
|
62
62
|
# Returns an item from queue and sends it to the client.
|
63
63
|
# If there is no item to fetch send an 204 (NoContent) and same as HEAD
|
64
64
|
def process_get(request, response, queue_path)
|
65
|
-
|
65
|
+
message = @queue_manager.poll(queue_path)
|
66
66
|
|
67
|
-
|
67
|
+
unless message.nil? then
|
68
68
|
response.start(200) do |head,out|
|
69
69
|
@log.debug("[MongrelHandler] Response to GET (200)")
|
70
|
-
head["
|
71
|
-
head["
|
72
|
-
head["
|
73
|
-
|
74
|
-
|
75
|
-
|
70
|
+
head["CONTENT-TYPE"] = message.content_type
|
71
|
+
head["SERVER"] = SERVER_HEADER
|
72
|
+
head["QUEUE_SIZE"] = @queue_manager.queue_size(queue_path)
|
73
|
+
head["QUEUE_BYTES"] = @queue_manager.queue_bytes(queue_path)
|
74
|
+
|
75
|
+
# send all options of the message back to the client
|
76
|
+
if message.respond_to?(:option) && message.option.size > 0
|
77
|
+
for option_name in message.option.keys
|
78
|
+
head["MESSAGE_#{option_name.gsub("-", "_").upcase}"] = message.option[option_name].to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if !message.payload.nil? && message.bytes > 0
|
83
|
+
@log.debug("[MongrelHandler] Message payload: #{message.payload}")
|
84
|
+
out.write(message.payload)
|
76
85
|
end
|
77
86
|
end
|
78
87
|
else
|
79
88
|
response.start(204) do |head,out|
|
80
89
|
@log.debug("[MongrelHandler] Response to GET (204)")
|
81
|
-
head["
|
82
|
-
head["
|
90
|
+
head["SERVER"] = SERVER_HEADER
|
91
|
+
head["QUEUE_SIZE"] = head["QUEUE_BYTES"] = 0
|
83
92
|
end
|
84
93
|
end
|
85
94
|
end
|
@@ -87,13 +96,22 @@ module FreeMessageQueue
|
|
87
96
|
# Put new item to the queue and and return sam e as head action (HTTP 200)
|
88
97
|
def process_post(request, response, queue_path)
|
89
98
|
@log.debug("[MongrelHandler] Response to POST (200)")
|
90
|
-
|
91
|
-
@log.debug("[MongrelHandler]
|
92
|
-
|
99
|
+
message = Message.new(request.body.read, request.params["CONTENT_TYPE"])
|
100
|
+
@log.debug("[MongrelHandler] Message payload: #{message.payload}")
|
101
|
+
|
102
|
+
# send all options of the message back to the client
|
103
|
+
for option_name in request.params.keys
|
104
|
+
if option_name.match(/HTTP_MESSAGE_([a-zA-Z][a-zA-Z0-9_\-]*)/)
|
105
|
+
message.option[$1] = request.params[option_name]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
@queue_manager.put(queue_path, message)
|
93
110
|
|
94
111
|
response.start(200) do |head,out|
|
95
|
-
head["
|
96
|
-
head["
|
112
|
+
head["SERVER"] = SERVER_HEADER
|
113
|
+
head["QUEUE_SIZE"] = @queue_manager.queue_size(queue_path)
|
114
|
+
head["QUEUE_BYTES"] = @queue_manager.queue_bytes(queue_path)
|
97
115
|
end
|
98
116
|
end
|
99
117
|
|
@@ -102,8 +120,9 @@ module FreeMessageQueue
|
|
102
120
|
@log.debug("[MongrelHandler] Response to HEAD (200)")
|
103
121
|
|
104
122
|
response.start(200) do |head,out|
|
105
|
-
head["
|
106
|
-
head["
|
123
|
+
head["SERVER"] = SERVER_HEADER
|
124
|
+
head["QUEUE_SIZE"] = @queue_manager.queue_size(queue_path)
|
125
|
+
head["QUEUE_BYTES"] = @queue_manager.queue_bytes(queue_path)
|
107
126
|
end
|
108
127
|
end
|
109
128
|
|
@@ -113,7 +132,7 @@ module FreeMessageQueue
|
|
113
132
|
@queue_manager.delete_queue(queue_path)
|
114
133
|
|
115
134
|
response.start(200) do |head,out|
|
116
|
-
head["
|
135
|
+
head["SERVER"] = SERVER_HEADER
|
117
136
|
end
|
118
137
|
end
|
119
138
|
|
@@ -123,8 +142,8 @@ module FreeMessageQueue
|
|
123
142
|
def client_exception(request, response, queue_path, ex)
|
124
143
|
@log.warn("[MongrelHandler] Client error: #{ex}")
|
125
144
|
response.start(400) do |head,out|
|
126
|
-
head["
|
127
|
-
head["
|
145
|
+
head["SERVER"] = SERVER_HEADER
|
146
|
+
head["ERROR"] = ex.message
|
128
147
|
end
|
129
148
|
end
|
130
149
|
|
@@ -139,9 +158,9 @@ module FreeMessageQueue
|
|
139
158
|
end
|
140
159
|
|
141
160
|
response.start(500) do |head,out|
|
142
|
-
head["
|
143
|
-
head["
|
144
|
-
head["
|
161
|
+
head["CONTENT-TYPE"] = "text/plain"
|
162
|
+
head["SERVER"] = SERVER_HEADER
|
163
|
+
head["ERROR"] = ex.message
|
145
164
|
|
146
165
|
out.write(ex.message + "\r\n\r\n")
|
147
166
|
for line in ex.backtrace
|