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/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