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/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("Error")) }
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("Error")) }
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("Error"));
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("Error"));
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("Error")) }
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("Error")) }
133
+ onFailure: function(transport){ alert(transport.getHeader("ERROR")) }
134
134
  });
135
135
  }
136
136
  </script>
@@ -1,20 +1,11 @@
1
- require "ostruct"
2
-
3
- class MyTestQueue
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
- item = OpenStruct.new
17
- item.data = "Hello World"
18
- item
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/urgent_messages"
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
- # msg = my_remote_queue.get()
36
- # puts " == URGENT MESSSAGE == "
37
- # puts msg
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
- # msg = "
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
- # my_remote_queue.put(msg)
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
- url = URI.parse(@url)
57
- req = Net::HTTP::Get.new(url.path)
58
- res = Net::HTTP.start(url.host, url.port) do |http|
59
- http.request(req)
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
- res.body
67
+
68
+ return message
62
69
  end
63
70
 
64
71
  alias get poll
65
72
 
66
- # this puts one message to the queue as a string
67
- def put(data)
68
- url = URI.parse(@url)
69
- res = Net::HTTP.start(url.host, url.port) do |http|
70
- http.post(url.path, data)
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
@@ -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 data: #{YAML.dump(request.params)})")
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
- queue_item = @queue_manager.poll(queue_path)
65
+ message = @queue_manager.poll(queue_path)
66
66
 
67
- if queue_item then
67
+ unless message.nil? then
68
68
  response.start(200) do |head,out|
69
69
  @log.debug("[MongrelHandler] Response to GET (200)")
70
- head["Content-Type"] = (queue_item.respond_to?(:content_type)) ? queue_item.content_type : "text/plain"
71
- head["Server"] = SERVER_HEADER
72
- head["Queue-Size"] = @queue_manager.queue_size(queue_path)
73
- if !queue_item.data.nil? && queue_item.data.size > 0
74
- @log.debug("[MongrelHandler] Response data: #{queue_item.data}")
75
- out.write(queue_item.data)
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["Server"] = SERVER_HEADER
82
- head["Queue-Size"] = 0
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
- data = request.body.read
91
- @log.debug("[MongrelHandler] DATA: #{data}")
92
- @queue_manager.put(queue_path, data)
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["Server"] = SERVER_HEADER
96
- head["Queue-Size"] = @queue_manager.queue_size(queue_path)
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["Server"] = SERVER_HEADER
106
- head["Queue-Size"] = @queue_manager.queue_size(queue_path)
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["Server"] = SERVER_HEADER
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["Server"] = SERVER_HEADER
127
- head["Error"] = ex.message
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["Content-Type"] = "text/plain"
143
- head["Server"] = SERVER_HEADER
144
- head["Error"] = ex.message
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