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