amq-protocol 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ /*.gem
2
+ .rvmrc
3
+ tmp
4
+ *.pyc
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "vendor/rabbitmq-codegen"]
2
+ path = vendor/rabbitmq-codegen
3
+ url = git://github.com/rabbitmq/rabbitmq-codegen.git
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format
3
+ progress
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ = Version 0.0.1
2
+ * [FEATURE] Basic code generation.
3
+ * [FEATURE] Generate only methods which are actually required by the client.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jakub Šťastný aka Botanicus
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,32 @@
1
+ h1. About
2
+
3
+ This is an AMQP parser for AMQP 0.9.1. It isn't an AMQP client, just the parser, so if you want to write your own AMQP client without digging into the protocol, this gem can help you with that.
4
+
5
+ h1. Usage
6
+
7
+ <pre>
8
+ gem install amqp-parser
9
+ </pre>
10
+
11
+ Check the @examples@ directory for some examples.
12
+
13
+ h1. Development
14
+
15
+ <pre>
16
+ git clone git://github.com/botanicus/amq-protocol.git --recursive
17
+ </pre>
18
+
19
+ Or, if you are on an older version of Git:
20
+
21
+ <pre>
22
+ git clone git://github.com/botanicus/amq-protocol.git
23
+ git submodule update --init
24
+ </pre>
25
+
26
+ If you want to change some code, don't do so in @lib/amq/protocol.rb@, but in @protocol.rb.pytemplate@ which is a template file for @codegen.py@. You can re-generate the code by @./tasks.rb generate@.
27
+
28
+ h1. Links
29
+
30
+ * "RDoc.info API Docs":http://rdoc.info/github/botanicus/amq-protocol/master/frames
31
+ * "Examples":https://github.com/botanicus/amq-protocol/tree/master/examples/
32
+ * "Bug reporting":http://github.com/botanicus/amq-protocol/issues
data/TODO.todo ADDED
@@ -0,0 +1,3 @@
1
+ - YARD documentation
2
+ - benchmarks
3
+ - optimisations (see puka)
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env gem build
2
+ # encoding: utf-8
3
+
4
+ require "base64"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "amq-protocol"
8
+ s.version = "0.0.1"
9
+ s.authors = ["Jakub Stastny"]
10
+ s.homepage = "http://github.com/botanicus/amq-protocol"
11
+ s.summary = "AMQP 0.9.1 encoder & decoder."
12
+ s.description = "This is an AMQP encoder & decoder for AMQP 0.9.1. It isn't an AMQP client, just the parser, so if you want to write your own AMQP client without digging into the protocol, this gem can help you with that."
13
+ s.cert_chain = nil
14
+ s.email = Base64.decode64("c3Rhc3RueUAxMDFpZGVhcy5jeg==\n")
15
+ s.has_rdoc = true
16
+
17
+ # files
18
+ s.files = `git ls-files`.split("\n")
19
+ s.require_paths = ["lib"]
20
+
21
+ # Ruby version
22
+ s.required_ruby_version = ::Gem::Requirement.new("~> 1.9")
23
+
24
+ begin
25
+ require "changelog"
26
+ rescue LoadError
27
+ warn "You have to have changelog gem installed for post install message"
28
+ else
29
+ s.post_install_message = CHANGELOG.new.version_changes
30
+ end
31
+
32
+ # RubyForge
33
+ s.rubyforge_project = "amq-protocol"
34
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env gem build
2
+ # encoding: utf-8
3
+
4
+ eval(File.read("amq-protocol.gemspec")).tap do |specification|
5
+ specification.version = "#{specification.version}.pre"
6
+ end
@@ -0,0 +1 @@
1
+ {"tx": {"select-ok": ["client"], "rollback": ["server"], "commit": ["server"], "rollback-ok": ["client"], "select": ["server"], "commit-ok": ["client"]}, "exchange": {"delete-ok": ["client"], "declare-ok": ["client"], "declare": ["server"], "delete": ["server"], "bind": ["server"], "bind-ok": ["client"], "unbind": ["server"], "unbind-ok": ["client"]}, "queue": {"unbind": ["server"], "unbind-ok": ["client"], "purge-ok": ["client"], "bind": ["server"], "purge": ["server"], "declare-ok": ["client"], "delete-ok": ["client"], "delete": ["server"], "declare": ["server"], "bind-ok": ["client"]}, "connection": {"secure": ["client"], "secure-ok": ["server"], "open-ok": ["client"], "close-ok": ["client", "server"], "start": ["client"], "tune": ["client"], "start-ok": ["server"], "close": ["client", "server"], "open": ["server"], "tune-ok": ["server"]}, "basic": {"qos": ["server"], "consume": ["server"], "reject": ["server"], "get": ["server"], "ack": ["server"], "get-ok": ["client"], "consume-ok": ["client"], "deliver": ["client"], "recover-ok": ["client"], "publish": ["server"], "cancel": ["server"], "recover-async": ["server"], "get-empty": ["client"], "qos-ok": ["client"], "return": ["client"], "recover": ["server"], "cancel-ok": ["client"]}, "channel": {"flow-ok": ["server", "client"], "flow": ["server", "client"], "open-ok": ["client"], "close-ok": ["client", "server"], "close": ["client", "server"], "open": ["server"]}}
data/benchmark.rb ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: binary
3
+
4
+ begin
5
+ require "amq/spec"
6
+ rescue LoadError
7
+ abort "You have to install the amqp gem in order to run the benchmark against its AMQP encoder/decoder."
8
+ end
9
+
10
+ require "benchmark"
11
+
12
+ Benchmark.bmbm do |bm|
13
+ bm.report("Raw binary") do
14
+ # TODO
15
+ end
16
+
17
+ bm.report("AMQP Gem") do
18
+ # TODO
19
+ end
20
+
21
+ bm.report("AMQ Protocol Gem") do
22
+ # TODO
23
+ end
24
+ end
data/codegen.py ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import os, sys, re
5
+
6
+ sys.path.append(os.path.join("vendor", "rabbitmq-codegen"))
7
+
8
+ from amqp_codegen import *
9
+ try:
10
+ from mako.template import Template
11
+ except ImportError:
12
+ print "Mako isn't installed. Run easy_install mako."
13
+ sys.exit(1)
14
+
15
+ # main class
16
+ class AmqpSpecObject(AmqpSpec):
17
+ IGNORED_CLASSES = ["access", "tx"]
18
+ IGNORED_FIELDS = {
19
+ 'ticket': 0,
20
+ 'nowait': 0,
21
+ 'capabilities': '',
22
+ 'insist' : 0,
23
+ 'out_of_band': '',
24
+ 'known_hosts': '',
25
+ }
26
+
27
+ def __init__(self, path):
28
+ AmqpSpec.__init__(self, path)
29
+
30
+ def extend_field(field):
31
+ field.ruby_name = re.sub("[- ]", "_", field.name)
32
+ field.type = self.resolveDomain(field.domain)
33
+ field.banned = bool(field.name in self.__class__.IGNORED_FIELDS)
34
+
35
+ for klass in self.classes:
36
+ klass.banned = bool(klass.name in self.__class__.IGNORED_CLASSES)
37
+
38
+ for field in klass.fields:
39
+ extend_field(field)
40
+
41
+ for method in klass.methods:
42
+ for field in method.arguments:
43
+ extend_field(field)
44
+
45
+ self.classes = filter(lambda klass: not klass.banned, self.classes)
46
+
47
+ # I know, I'm a bad, bad boy, but come on guys,
48
+ # monkey-patching is just handy for this case.
49
+ # Oh hell, why Python doesn't have at least
50
+ # anonymous functions? This looks so ugly.
51
+ original_init = AmqpEntity.__init__
52
+ def new_init(self, arg):
53
+ original_init(self, arg)
54
+ constant_name = ""
55
+ for chunk in self.name.split("-"):
56
+ constant_name += chunk.capitalize()
57
+ self.constant_name = constant_name
58
+ AmqpEntity.__init__ = new_init
59
+
60
+ # method.accepted_by("server")
61
+ # method.accepted_by("client", "server")
62
+ accepted_by_update = json.loads(file("amqp_0.9.1_changes.json").read())
63
+
64
+ def accepted_by(self, *receivers):
65
+ def get_accepted_by(self):
66
+ try:
67
+ return accepted_by_update[self.klass.name][self.name]
68
+ except KeyError:
69
+ return ["server", "client"]
70
+
71
+ actual_receivers = get_accepted_by(self)
72
+ return all(map(lambda receiver: receiver in actual_receivers, receivers))
73
+
74
+ AmqpMethod.accepted_by = accepted_by
75
+
76
+ def convert_to_ruby(field):
77
+ name = re.sub("-", "_", field.name) # TODO: use ruby_name
78
+ if field.defaultvalue == None:
79
+ return "%s = nil" % (name,)
80
+ elif field.defaultvalue == False:
81
+ return "%s = false" % (name,)
82
+ elif field.defaultvalue == True:
83
+ return "%s = true" % (name,)
84
+ else:
85
+ return "%s = %r" % (name, field.defaultvalue)
86
+
87
+ def params(self):
88
+ buffer = []
89
+ for f in self.arguments:
90
+ buffer.append(convert_to_ruby(f))
91
+ if self.hasContent:
92
+ buffer.append("user_headers = nil")
93
+ buffer.append("payload = \"\"")
94
+ buffer.append("frame_size = nil")
95
+ return buffer
96
+
97
+ AmqpMethod.params = params
98
+
99
+ def args(self):
100
+ return map(lambda item: item.split(" ")[0], self.params())
101
+
102
+ AmqpMethod.args = args
103
+
104
+ def binary(self):
105
+ method_id = self.klass.index << 16 | self.index
106
+ return "0x%08X # %i, %i, %i" % (method_id, self.klass.index, self.index, method_id)
107
+
108
+ AmqpMethod.binary = binary
109
+
110
+ # helpers
111
+ def render(path, **context):
112
+ file = open(path)
113
+ template = Template(file.read())
114
+ return template.render(**context)
115
+
116
+ def main(json_spec_path):
117
+ spec = AmqpSpecObject(json_spec_path)
118
+ print render("protocol.rb.pytemplate", spec = spec)
119
+
120
+ if __name__ == "__main__":
121
+ do_main_dict({"spec": main})
@@ -0,0 +1,113 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ def genSingleEncode(spec, cValue, unresolved_domain):
4
+ buffer = []
5
+ type = spec.resolveDomain(unresolved_domain)
6
+ if type == 'shortstr':
7
+ buffer.append("pieces << %s.bytesize.chr" % (cValue,))
8
+ buffer.append("pieces << %s" % (cValue,))
9
+ elif type == 'longstr':
10
+ buffer.append("pieces << [%s.bytesize].pack('N')" % (cValue,))
11
+ buffer.append("pieces << %s" % (cValue,))
12
+ elif type == 'octet':
13
+ buffer.append("pieces << [%s].pack('B')" % (cValue,))
14
+ elif type == 'short':
15
+ buffer.append("pieces << [%s].pack('n')" % (cValue,))
16
+ elif type == 'long':
17
+ buffer.append("pieces << [%s].pack('N')" % (cValue,))
18
+ elif type == 'longlong':
19
+ buffer.append("pieces << [%s].pack('>Q')" % (cValue,))
20
+ elif type == 'timestamp':
21
+ buffer.append("pieces << [%s].pack('>Q')" % (cValue,))
22
+ elif type == 'bit':
23
+ raise "Can't encode bit in genSingleEncode"
24
+ elif type == 'table':
25
+ buffer.append("pieces << AMQ::Protocol::Table.encode(%s)" % (cValue,))
26
+ else:
27
+ raise "Illegal domain in genSingleEncode", type
28
+
29
+ return buffer
30
+
31
+ def genSingleDecode(spec, cLvalue, unresolved_domain):
32
+ type = spec.resolveDomain(unresolved_domain)
33
+ buffer = []
34
+ if type == 'shortstr':
35
+ buffer.append("length = data[offset..(offset + 1)].unpack('N')[0]")
36
+ buffer.append("offset += 1")
37
+ buffer.append("%s = data[offset..(offset + length)]" % (cLvalue,))
38
+ buffer.append("offset += length")
39
+ elif type == 'longstr':
40
+ buffer.append("length = data[offset..(offset + 4)].unpack('N').first")
41
+ buffer.append("offset += 4")
42
+ buffer.append("%s = data[offset..(offset + length)]" % (cLvalue,))
43
+ buffer.append("offset += length")
44
+ elif type == 'octet':
45
+ buffer.append("%s = data[offset...(offset + 1)].unpack('c').first" % (cLvalue,))
46
+ buffer.append("offset += 1")
47
+ elif type == 'short':
48
+ buffer.append("%s = data[offset..(offset + 2)].unpack('n').first" % (cLvalue,))
49
+ buffer.append("offset += 2")
50
+ elif type == 'long':
51
+ buffer.append("%s = data[offset..(offset + 4)].unpack('N').first" % (cLvalue,))
52
+ buffer.append("offset += 4")
53
+ elif type == 'longlong':
54
+ buffer.append("%s = data[offset..(offset + 8)].unpack('N2').first" % (cLvalue,))
55
+ buffer.append("offset += 8")
56
+ elif type == 'timestamp':
57
+ buffer.append("%s = data[offset..(offset + 8)].unpack('N2').first" % (cLvalue,))
58
+ buffer.append("offset += 8")
59
+ elif type == 'bit':
60
+ raise "Can't decode bit in genSingleDecode"
61
+ elif type == 'table':
62
+ buffer.append("table_length = Table.length(data[offset..(offset + 4)])")
63
+ buffer.append("%s = Table.decode(data[offset..table_length])" % (cLvalue,))
64
+ else:
65
+ raise "Illegal domain in genSingleDecode", type
66
+
67
+ return buffer
68
+
69
+ def genEncodeMethodDefinition(spec, m):
70
+ def finishBits():
71
+ if bit_index is not None:
72
+ return "pieces << [bit_buffer].pack('B')"
73
+
74
+ bit_index = None
75
+ buffer = []
76
+
77
+ for f in m.arguments:
78
+ if spec.resolveDomain(f.domain) == 'bit':
79
+ if bit_index is None:
80
+ bit_index = 0
81
+ buffer.append("bit_buffer = 0")
82
+ if bit_index >= 8:
83
+ finishBits()
84
+ buffer.append("bit_buffer = 0")
85
+ bit_index = 0
86
+ buffer.append("bit_buffer = bit_buffer | (1 << %d) if %s" % (bit_index, f.ruby_name))
87
+ bit_index = bit_index + 1
88
+ else:
89
+ finishBits()
90
+ bit_index = None
91
+ buffer += genSingleEncode(spec, f.ruby_name, f.domain)
92
+
93
+ finishBits()
94
+ return buffer
95
+
96
+ def genDecodeMethodDefinition(spec, m):
97
+ buffer = []
98
+ bitindex = None
99
+ for f in m.arguments:
100
+ if spec.resolveDomain(f.domain) == 'bit':
101
+ if bitindex is None:
102
+ bitindex = 0
103
+ if bitindex >= 8:
104
+ bitindex = 0
105
+ if bitindex == 0:
106
+ buffer.append("bit_buffer = data[offset..(offset + 1)].unpack('c').first")
107
+ buffer.append("offset += 1")
108
+ buffer.append("%s = (bit_buffer & (1 << %d)) != 0" % (f.ruby_name, bitindex))
109
+ bitindex = bitindex + 1
110
+ else:
111
+ bitindex = None
112
+ buffer += genSingleDecode(spec, f.ruby_name, f.domain)
113
+ return buffer
@@ -0,0 +1,60 @@
1
+ # encoding: binary
2
+
3
+ require "socket"
4
+ require_relative "../lib/amq/protocol.rb"
5
+
6
+ include AMQ::Protocol
7
+
8
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
9
+ sockaddr = Socket.pack_sockaddr_in((ARGV.first || 5672).to_i, "127.0.0.1") # NOTE: this doesn't work with "localhost", I don't know why.
10
+
11
+ begin
12
+ socket.connect(sockaddr)
13
+ rescue Errno::ECONNREFUSED
14
+ abort "Don't forget to start an AMQP broker first!"
15
+ end
16
+
17
+ # helpers
18
+ MethodFrame.encode(:method, 0, Connection::TuneOk.encode(0, 131072, 0))
19
+
20
+ def socket.encode(klass, *args)
21
+ STDERR.puts "#{klass}.encode(#{args.inspect[1..-2]})"
22
+ klass.encode(*args).tap do |result|
23
+ STDERR.puts "=> #{result.inspect}"
24
+ STDERR.puts "MethodFrame.encode(:method, 0, #{result.inspect})"
25
+ STDERR.puts "=> #{MethodFrame.encode(:method, 0, result).inspect}\n\n"
26
+ self.write(MethodFrame.encode(:method, 0, result))
27
+ end
28
+ end
29
+
30
+ def socket.decode
31
+ frame = MethodFrame.decode(self)
32
+ STDERR.puts "MethodFrame.decode(#{self.inspect})"
33
+ STDERR.puts "=> #{frame.inspect}\n\n"
34
+ end
35
+
36
+ # AMQP preamble
37
+ puts "Sending AMQP preamble (#{AMQ::Protocol::PREAMBLE.inspect})\n\n"
38
+ socket.write AMQ::Protocol::PREAMBLE
39
+
40
+ # Start/Start-Ok
41
+ socket.decode
42
+ socket.encode Connection::StartOk, {client: "AMQ Protocol"}, "PLAIN", "guest\0guest\0", "en_GB"
43
+
44
+ # Tune/Tune-Ok
45
+ socket.decode
46
+ socket.encode Connection::TuneOk, 0, 131072, 0
47
+
48
+ # Close
49
+ # socket.encode Connection::Close
50
+ # socket.decode
51
+
52
+ socket.close
53
+
54
+ __END__
55
+ [CLIENT] conn#4 ch#0 -> {#method<connection.start-ok>(client-properties={product=AMQP, information=http://github.com/tmm1/amqp, platform=Ruby/EventMachine, version=0.6.7},mechanism=AMQPLAIN,response=LOGINSguesPASSWORDSguest,locale=en_US),null,""}
56
+ [SERVER] conn#4 ch#0 <- {#method<connection.start>(version-major=8,version-minor=0,server properties={product=RabbitMQ, information=Licensed under the MPL. See http://www.rabbitmq.com/, platform=Erlang/OTP, copyright=Copyright (C) 2007-2010 LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd., version=2.1.0},mechanisms=PLAIN AMQPLAIN,locales=en_US),null,""}
57
+ [SERVER] conn#4 ch#0 <- {#method<connection.tune>(channel-max=0,frame-max=131072,heartbeat=0),null,""}
58
+ [CLIENT] conn#4 ch#0 -> {#method<connection.tune-ok>(channel-max=0,frame-max=131072,heartbeat=0),null,""}
59
+ [CLIENT] conn#4 ch#0 -> {#method<connection.open>(virtual-host=/,capabilities=,insist=false),null,""}
60
+ [SERVER] conn#4 ch#0 <- {#method<connection.open-ok>(known-hosts=),null,""}