reliable-msg 1.0.1 → 1.1.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 +5 -1
- data/Rakefile +30 -23
- data/changelog.txt +32 -0
- data/lib/reliable-msg.rb +10 -5
- data/lib/reliable-msg/cli.rb +47 -3
- data/lib/reliable-msg/client.rb +213 -0
- data/lib/reliable-msg/message-store.rb +128 -49
- data/lib/reliable-msg/mysql.sql +7 -1
- data/lib/reliable-msg/queue-manager.rb +263 -58
- data/lib/reliable-msg/queue.rb +100 -253
- data/lib/reliable-msg/rails.rb +114 -0
- data/lib/reliable-msg/selector.rb +65 -75
- data/lib/reliable-msg/topic.rb +215 -0
- data/test/test-queue.rb +35 -5
- data/test/test-rails.rb +59 -0
- data/test/test-topic.rb +102 -0
- metadata +54 -41
- data/lib/uuid.rb +0 -384
- data/test/test-uuid.rb +0 -48
data/README
CHANGED
@@ -28,7 +28,7 @@ The latest version of Reliable Messaging can be found at
|
|
28
28
|
|
29
29
|
For more information
|
30
30
|
|
31
|
-
* http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/
|
31
|
+
* http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
|
32
32
|
|
33
33
|
|
34
34
|
== Installation
|
@@ -113,6 +113,10 @@ Stop the queue manager
|
|
113
113
|
|
114
114
|
queues manager stop
|
115
115
|
|
116
|
+
== Change log
|
117
|
+
|
118
|
+
:include: changelog.txt
|
119
|
+
|
116
120
|
|
117
121
|
== License
|
118
122
|
|
data/Rakefile
CHANGED
@@ -1,30 +1,35 @@
|
|
1
1
|
# Adapted from the rake Rakefile.
|
2
2
|
|
3
|
-
require
|
3
|
+
require "rubygems"
|
4
4
|
Gem::manage_gems
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
5
|
+
require "rake/testtask"
|
6
|
+
require "rake/rdoctask"
|
7
|
+
require "rake/gempackagetask"
|
8
8
|
|
9
9
|
|
10
10
|
desc "Default Task"
|
11
11
|
task :default => [:tests, :rdoc]
|
12
12
|
|
13
13
|
|
14
|
-
desc "Run test case for
|
15
|
-
Rake::TestTask.new :
|
14
|
+
desc "Run test case for Queue API"
|
15
|
+
Rake::TestTask.new :test_queue do |test|
|
16
16
|
test.verbose = true
|
17
|
-
test.test_files = [
|
17
|
+
test.test_files = ["test/test-queue.rb"]
|
18
18
|
end
|
19
|
-
desc "Run test case for
|
20
|
-
Rake::TestTask.new :
|
19
|
+
desc "Run test case for Topic API"
|
20
|
+
Rake::TestTask.new :test_topic do |test|
|
21
|
+
test.verbose = true
|
22
|
+
test.test_files = ["test/test-topic.rb"]
|
23
|
+
end
|
24
|
+
desc "Run test case for Rails integration"
|
25
|
+
Rake::TestTask.new :test_rails do |test|
|
21
26
|
test.verbose = true
|
22
|
-
test.test_files = [
|
27
|
+
test.test_files = ["test/test-rails.rb"]
|
23
28
|
end
|
24
29
|
desc "Run all test cases"
|
25
|
-
Rake::TestTask.new :tests
|
30
|
+
Rake::TestTask.new :tests do |test|
|
26
31
|
test.verbose = true
|
27
|
-
test.test_files = []
|
32
|
+
test.test_files = ["test/*.rb"]
|
28
33
|
#test.warning = true
|
29
34
|
end
|
30
35
|
|
@@ -33,7 +38,7 @@ end
|
|
33
38
|
Rake::RDocTask.new do |rdoc|
|
34
39
|
rdoc.main = "README"
|
35
40
|
rdoc.rdoc_files.include("README", "lib/**/*.rb")
|
36
|
-
rdoc.title =
|
41
|
+
rdoc.title = "Reliable Messaging"
|
37
42
|
end
|
38
43
|
|
39
44
|
# Handle version number.
|
@@ -43,7 +48,7 @@ class Version
|
|
43
48
|
|
44
49
|
def initialize file, new_version
|
45
50
|
@file = file
|
46
|
-
@version = File.open @file,
|
51
|
+
@version = File.open @file, "r" do |file|
|
47
52
|
version = nil
|
48
53
|
file.each_line do |line|
|
49
54
|
match = line.match PATTERN
|
@@ -78,7 +83,7 @@ class Version
|
|
78
83
|
input.each_line do |line|
|
79
84
|
match = line.match PATTERN
|
80
85
|
if match
|
81
|
-
output.puts "#{match[1]}VERSION =
|
86
|
+
output.puts "#{match[1]}VERSION = \"#{@new_version}\""
|
82
87
|
else
|
83
88
|
output.puts line
|
84
89
|
end
|
@@ -90,12 +95,12 @@ class Version
|
|
90
95
|
end
|
91
96
|
|
92
97
|
end
|
93
|
-
version = Version.new
|
98
|
+
version = Version.new "lib/reliable-msg.rb", ENV["version"]
|
94
99
|
|
95
100
|
|
96
101
|
# Create the GEM package.
|
97
102
|
gem_spec = Gem::Specification.new do |spec|
|
98
|
-
spec.name =
|
103
|
+
spec.name = "reliable-msg"
|
99
104
|
spec.version = version.next
|
100
105
|
spec.summary = "Reliable messaging and persistent queues for building asynchronous applications in Ruby"
|
101
106
|
spec.description = <<-EOF
|
@@ -108,18 +113,20 @@ gem_spec = Gem::Specification.new do |spec|
|
|
108
113
|
EOF
|
109
114
|
spec.author = "Assaf Arkin"
|
110
115
|
spec.email = "assaf@labnotes.org"
|
111
|
-
spec.homepage = "http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/
|
116
|
+
spec.homepage = "http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging"
|
112
117
|
|
113
|
-
spec.files = FileList["{bin,test,lib,docs}/**/*", "README", "MIT-LICENSE", "Rakefile"].to_a
|
118
|
+
spec.files = FileList["{bin,test,lib,docs}/**/*", "README", "MIT-LICENSE", "Rakefile", "changelog.txt"].to_a
|
114
119
|
spec.require_path = "lib"
|
115
|
-
spec.autorequire =
|
120
|
+
spec.autorequire = "reliable-msg.rb"
|
116
121
|
spec.bindir = "bin"
|
117
122
|
spec.executables = ["queues"]
|
118
123
|
spec.default_executable = "queues"
|
119
124
|
spec.requirements << "MySQL for database store, otherwise uses the file system"
|
120
125
|
spec.has_rdoc = true
|
121
|
-
spec.rdoc_options <<
|
126
|
+
spec.rdoc_options << "--main" << "README" << "--title" << "Reliable Messaging for Ruby" << "--line-numbers"
|
122
127
|
spec.extra_rdoc_files = ["README"]
|
128
|
+
spec.rubyforge_project = "reliable-msg"
|
129
|
+
spec.add_dependency(%q<uuid>, [">= 1.0.0"])
|
123
130
|
end
|
124
131
|
|
125
132
|
gem = Rake::GemPackageTask.new(gem_spec) do |pkg|
|
@@ -130,7 +137,7 @@ end
|
|
130
137
|
|
131
138
|
desc "Look for TODO and FIXME tags in the code"
|
132
139
|
task :todo do
|
133
|
-
FileList[
|
140
|
+
FileList["**/*.rb"].egrep /#.*(FIXME|TODO|TBD)/
|
134
141
|
end
|
135
142
|
|
136
143
|
|
@@ -148,7 +155,7 @@ task :release => [:tests, :prerelease, :clobber, :update_version, :package] do
|
|
148
155
|
end
|
149
156
|
|
150
157
|
task :prerelease do
|
151
|
-
if !version.changed? && ENV[
|
158
|
+
if !version.changed? && ENV["reuse"] != version.number
|
152
159
|
fail "Current version is #{version.number}, must specify reuse=ver to reuse existing version"
|
153
160
|
end
|
154
161
|
end
|
data/changelog.txt
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Release 1.1.0 (Nov 26, 2005)
|
2
|
+
|
3
|
+
* Added: Topic class for publishing messages on a topic.
|
4
|
+
* Added: Can set delivery option when creating a queue.
|
5
|
+
* Added: Queue.name() returns the queue's name.
|
6
|
+
* Added: Command line options to list or delete all messages
|
7
|
+
in named queue.
|
8
|
+
* Added: Rails integration for easily accessing queues/topics
|
9
|
+
from a Rails controller.
|
10
|
+
* Changed: Quque and Topic both extend the base class Client.
|
11
|
+
* Changed: Cannot start two queue managers in the same process.
|
12
|
+
* Changed: Each message has a created header indicating date/time
|
13
|
+
of creation. Received header no longer exists.
|
14
|
+
* Changed: Header retry renamed to delivered, to prevent clash with
|
15
|
+
reserved Ruby keyword.
|
16
|
+
* Changed: Selectors are now executed in the client process.
|
17
|
+
Selectors can rely on client variables, methods and constants.
|
18
|
+
* Changed: Specify maximum delivery attempts with the header
|
19
|
+
max_deliveries; get the redelivery attempt from the header
|
20
|
+
redelivery; the later is only set on the first redelivery attempt.
|
21
|
+
* Fixed: Documentation errors in Queue.
|
22
|
+
* Removed: Cannot associate default selector with queue.
|
23
|
+
|
24
|
+
|
25
|
+
Release 1.0.1 (Nov 10, 2005)
|
26
|
+
|
27
|
+
* Added: Test cases test put/get in memory and by reloading.
|
28
|
+
* Fixed: Messages not retrieved in order after queue manager
|
29
|
+
recovers when using MySQL message store.
|
30
|
+
* Fixed: Queue manager fails if stopped and then started again.
|
31
|
+
|
32
|
+
|
data/lib/reliable-msg.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
-
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
-
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
3
|
|
4
4
|
module ReliableMsg
|
5
5
|
|
6
6
|
PACKAGE = "reliable-msg"
|
7
|
-
VERSION =
|
7
|
+
VERSION = "1.1.0"
|
8
8
|
|
9
9
|
end
|
10
10
|
|
11
|
-
require
|
12
|
-
require
|
11
|
+
require "reliable-msg/queue"
|
12
|
+
require "reliable-msg/topic"
|
13
|
+
require "reliable-msg/cli"
|
14
|
+
begin
|
15
|
+
require "reliable-msg/rails"
|
16
|
+
rescue LoadError
|
17
|
+
end
|
data/lib/reliable-msg/cli.rb
CHANGED
@@ -2,12 +2,11 @@
|
|
2
2
|
# = cli.rb - Reliable messaging command-line interface
|
3
3
|
#
|
4
4
|
# Author:: Assaf Arkin assaf@labnotes.org
|
5
|
-
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/
|
5
|
+
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
|
6
6
|
# Copyright:: Copyright (c) 2005 Assaf Arkin
|
7
7
|
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
8
8
|
#
|
9
9
|
#--
|
10
|
-
# Changes:
|
11
10
|
#++
|
12
11
|
|
13
12
|
|
@@ -43,6 +42,12 @@ Available commands:
|
|
43
42
|
manager stop
|
44
43
|
Stop a running queue manager.
|
45
44
|
|
45
|
+
list <queue>
|
46
|
+
List headers of messages in the named queue.
|
47
|
+
|
48
|
+
empty <queue>
|
49
|
+
Empty (delete all messages from) the named queue.
|
50
|
+
|
46
51
|
install disk [<path>]
|
47
52
|
Configure queue manager to use disk-based message store
|
48
53
|
using the specified directory. Uses 'queues' by default.
|
@@ -68,7 +73,9 @@ Available options:
|
|
68
73
|
|
69
74
|
EOF
|
70
75
|
|
71
|
-
|
76
|
+
MAX_STRING = 50
|
77
|
+
|
78
|
+
class InvalidUsage < Exception #:nodoc:
|
72
79
|
end
|
73
80
|
|
74
81
|
def initialize
|
@@ -149,6 +156,43 @@ EOF
|
|
149
156
|
raise InvalidUsage
|
150
157
|
end
|
151
158
|
|
159
|
+
when 'list'
|
160
|
+
queue = args[1]
|
161
|
+
raise InvalidUsage unless queue
|
162
|
+
begin
|
163
|
+
qm = queue_manager(config_file)
|
164
|
+
list = qm.list(:queue=>queue)
|
165
|
+
puts "Found #{list.size} messages in queue #{queue}"
|
166
|
+
list.each do |headers|
|
167
|
+
puts "Message #{headers[:id]}"
|
168
|
+
headers.each do |name, value|
|
169
|
+
unless name==:id
|
170
|
+
case value
|
171
|
+
when String
|
172
|
+
value = value[0..MAX_STRING - 3] << "..." if value.length > MAX_STRING
|
173
|
+
value = '"' << value.gsub('"', '\"') << '"'
|
174
|
+
when Symbol
|
175
|
+
value = ":#{value.to_s}"
|
176
|
+
end
|
177
|
+
puts " :#{name} => #{value}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
rescue DRb::DRbConnError =>error
|
182
|
+
puts "Cannot access queue manager: is it running?"
|
183
|
+
end
|
184
|
+
|
185
|
+
when 'empty'
|
186
|
+
queue = args[1]
|
187
|
+
raise InvalidUsage unless queue
|
188
|
+
begin
|
189
|
+
qm = queue_manager(config_file)
|
190
|
+
while msg = qm.enqueue(:queue=>queue)
|
191
|
+
end
|
192
|
+
rescue DRb::DRbConnError =>error
|
193
|
+
puts "Cannot access queue manager: is it running?"
|
194
|
+
end
|
195
|
+
|
152
196
|
else
|
153
197
|
raise InvalidUsage
|
154
198
|
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
#
|
2
|
+
# = client.rb - Base class for queue/topic API
|
3
|
+
#
|
4
|
+
# Author:: Assaf Arkin assaf@labnotes.org
|
5
|
+
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
|
6
|
+
# Copyright:: Copyright (c) 2005 Assaf Arkin
|
7
|
+
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
8
|
+
#
|
9
|
+
#--
|
10
|
+
#++
|
11
|
+
|
12
|
+
require 'drb'
|
13
|
+
require 'reliable-msg/selector'
|
14
|
+
|
15
|
+
|
16
|
+
module ReliableMsg
|
17
|
+
|
18
|
+
# Base class for both Queue and Topic client APIs.
|
19
|
+
class Client
|
20
|
+
|
21
|
+
ERROR_INVALID_SELECTOR = "Selector must be message identifier (String), set of header name/value pairs (Hash), Selector object, or nil" # :nodoc:
|
22
|
+
|
23
|
+
ERROR_INVALID_TX_TIMEOUT = "Invalid transaction timeout: must be a non-zero positive integer" # :nodoc:
|
24
|
+
|
25
|
+
ERROR_INVALID_CONNECT_COUNT = "Invalid connection count: must be a non-zero positive integer" # :nodoc:
|
26
|
+
|
27
|
+
ERROR_SELECTOR_VALUE_OR_BLOCK = "You can either pass a Selector object, or use a block" # :nodoc:
|
28
|
+
|
29
|
+
ERROR_INVALID_INIT_OPTION = "Unrecognized initialization option %s" #:nodoc:
|
30
|
+
|
31
|
+
# The default DRb port used to connect to the queue manager.
|
32
|
+
DRB_PORT = 6438
|
33
|
+
|
34
|
+
DEFAULT_DRB_URI = "druby://localhost:#{DRB_PORT}" #:nodoc:
|
35
|
+
|
36
|
+
# Number of times to retry a connecting to the queue manager.
|
37
|
+
DEFAULT_CONNECT_RETRY = 5
|
38
|
+
|
39
|
+
# Default transaction timeout.
|
40
|
+
DEFAULT_TX_TIMEOUT = 120
|
41
|
+
|
42
|
+
# Thread.current entry for queue transaction.
|
43
|
+
THREAD_CURRENT_TX = :reliable_msg_tx #:nodoc:
|
44
|
+
|
45
|
+
# The name of the dead letter queue (<tt>DLQ</tt>). Messages that expire or fail
|
46
|
+
# to process are automatically sent to the dead letter queue.
|
47
|
+
DLQ = DEAD_LETTER_QUEUE = "$dlq"
|
48
|
+
|
49
|
+
# DRb URI for queue manager. You can override this to change the URI globally,
|
50
|
+
# for all Queue objects that are not instantiated with an alternative URI.
|
51
|
+
@@drb_uri = DEFAULT_DRB_URI
|
52
|
+
|
53
|
+
# Reference to the local queue manager. Defaults to a DRb object, unless
|
54
|
+
# the queue manager is running locally.
|
55
|
+
@@qm = nil #:nodoc:
|
56
|
+
|
57
|
+
# Cache of queue managers referenced by their URI.
|
58
|
+
@@qm_cache = {} #:nodoc:
|
59
|
+
|
60
|
+
# Returns the transaction timeout (in seconds).
|
61
|
+
def tx_timeout
|
62
|
+
@tx_timeout || DEFAULT_TX_TIMEOUT
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sets the transaction timeout (in seconds). Affects future transactions started
|
66
|
+
# by Queue.get. Use +nil+ to restore the default timeout.
|
67
|
+
def tx_timeout= timeout
|
68
|
+
if timeout
|
69
|
+
raise ArgumentError, ERROR_INVALID_TX_TIMEOUT unless timeout.instance_of?(Integer) and timeout > 0
|
70
|
+
@tx_timeout = timeout
|
71
|
+
else
|
72
|
+
@tx_timeout = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the number of connection attempts, before operations fail.
|
77
|
+
def connect_count
|
78
|
+
@connect_count || DEFAULT_CONNECT_RETRY
|
79
|
+
end
|
80
|
+
|
81
|
+
# Sets the number of connection attempts, before operations fail. The minimum is one.
|
82
|
+
# Use +nil+ to restore the default connection count.
|
83
|
+
def connect_count= count
|
84
|
+
if count
|
85
|
+
raise ArgumentError, ERROR_INVALID_CONNECT_COUNT unless count.instance_of?(Integer) and count > 0
|
86
|
+
@connect_count = count
|
87
|
+
else
|
88
|
+
@connect_count = nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create and return a new selector based on the block expression. Same as
|
93
|
+
# Selector.new. For example:
|
94
|
+
# selector = Queue.selector { priority >= 2 and received > Time.new.to_i - 60 }
|
95
|
+
def self.selector &block
|
96
|
+
raise ArgumentError, ERROR_NO_SELECTOR_BLOCK unless block
|
97
|
+
Selector.new &block
|
98
|
+
end
|
99
|
+
|
100
|
+
# Create and return a new selector based on the block expression. Same as
|
101
|
+
# Selector.new. For example:
|
102
|
+
# selector = Queue.selector { priority >= 2 and received > Time.new.to_i - 60 }
|
103
|
+
def selector & block
|
104
|
+
raise ArgumentError, ERROR_NO_SELECTOR_BLOCK unless block
|
105
|
+
Selector.new &block
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Returns the active queue manager. You can override this method to implement
|
111
|
+
# load balancing.
|
112
|
+
def qm
|
113
|
+
if uri = @drb_uri
|
114
|
+
# Queue specifies queue manager's URI: use that queue manager.
|
115
|
+
@@qm_cache[uri] ||= DRbObject.new(nil, uri)
|
116
|
+
else
|
117
|
+
# Use the same queue manager for all queues, and cache it.
|
118
|
+
# Create only the first time.
|
119
|
+
@@qm ||= DRbObject.new(nil, @@drb_uri || DEFAULT_DRB_URI)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Called to execute the operation repeatedly and avoid connection failures. This only
|
124
|
+
# makes sense if we have a load balancing algorithm.
|
125
|
+
def repeated &block
|
126
|
+
count = connect_count
|
127
|
+
begin
|
128
|
+
block.call qm
|
129
|
+
rescue DRb::DRbConnError=>error
|
130
|
+
warn error
|
131
|
+
warn error.backtrace
|
132
|
+
retry if (count -= 1) > 0
|
133
|
+
raise error
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class << self
|
138
|
+
private
|
139
|
+
# Sets the active queue manager. Used when the queue manager is running in the
|
140
|
+
# same process to bypass DRb calls.
|
141
|
+
def qm= qm
|
142
|
+
@@qm = qm
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
# == Retrieved Message
|
150
|
+
#
|
151
|
+
# Returned from Queue.get holding the last message retrieved from the
|
152
|
+
# queue and providing access to the message identifier, headers and object.
|
153
|
+
#
|
154
|
+
# For example:
|
155
|
+
# while queue.get do |msg|
|
156
|
+
# print "Message #{msg.id}"
|
157
|
+
# print "Headers: #{msg[:created]}"
|
158
|
+
# print "Headers: #{msg.headers.inspect}"
|
159
|
+
# print msg.object
|
160
|
+
# true
|
161
|
+
# end
|
162
|
+
class Message
|
163
|
+
|
164
|
+
def initialize id, headers, object # :nodoc:
|
165
|
+
@id, @object, @headers = id, object, headers
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns the message identifier.
|
169
|
+
#
|
170
|
+
# :call-seq:
|
171
|
+
# msg.id -> id
|
172
|
+
#
|
173
|
+
def id
|
174
|
+
@id
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the message object.
|
178
|
+
#
|
179
|
+
# :call-seq:
|
180
|
+
# msg.object -> obj
|
181
|
+
#
|
182
|
+
def object
|
183
|
+
@object
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns the message headers.
|
187
|
+
#
|
188
|
+
# :call-seq:
|
189
|
+
# msg.headers -> hash
|
190
|
+
#
|
191
|
+
def headers
|
192
|
+
@headers
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns the message header.
|
196
|
+
#
|
197
|
+
# :call-seq:
|
198
|
+
# msg[symbol] -> obj
|
199
|
+
#
|
200
|
+
def [] symbol
|
201
|
+
@headers[symbol]
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
def method_missing symbol, *args, &block
|
206
|
+
raise ArgumentError, "Wrong number of arguments (#{args.length} for 0)" unless args.empty?
|
207
|
+
@headers[symbol]
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|