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 +4 -0
- data/.gitmodules +3 -0
- data/.rspec +3 -0
- data/CHANGELOG +3 -0
- data/LICENSE +20 -0
- data/README.textile +32 -0
- data/TODO.todo +3 -0
- data/amq-protocol.gemspec +34 -0
- data/amq-protocol.pre.gemspec +6 -0
- data/amqp_0.9.1_changes.json +1 -0
- data/benchmark.rb +24 -0
- data/codegen.py +121 -0
- data/codegen_helpers.py +113 -0
- data/examples/00_manual_test.rb +60 -0
- data/examples/01_basics.rb +14 -0
- data/examples/02_eventmachine.rb +4 -0
- data/irb.rb +9 -0
- data/lib/amq/protocol.rb +1473 -0
- data/lib/amq/protocol/frame.rb +97 -0
- data/lib/amq/protocol/table.rb +94 -0
- data/post-processing.rb +24 -0
- data/protocol.rb.pytemplate +253 -0
- data/spec/amq/protocol/frame_spec.rb +82 -0
- data/spec/amq/protocol/table_spec.rb +46 -0
- data/spec/amq/protocol_spec.rb +888 -0
- data/spec/spec_helper.rb +8 -0
- data/tasks.rb +23 -0
- metadata +94 -0
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/.rspec
ADDED
data/CHANGELOG
ADDED
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,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 @@
|
|
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})
|
data/codegen_helpers.py
ADDED
@@ -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,""}
|