vx-lib-consumer 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +30 -0
- data/Rakefile +1 -0
- data/lib/vx/lib/consumer/ack.rb +44 -0
- data/lib/vx/lib/consumer/configuration.rb +69 -0
- data/lib/vx/lib/consumer/error.rb +10 -0
- data/lib/vx/lib/consumer/instrument.rb +27 -0
- data/lib/vx/lib/consumer/params.rb +80 -0
- data/lib/vx/lib/consumer/publish.rb +47 -0
- data/lib/vx/lib/consumer/rpc.rb +217 -0
- data/lib/vx/lib/consumer/serializer.rb +90 -0
- data/lib/vx/lib/consumer/session.rb +168 -0
- data/lib/vx/lib/consumer/subscribe.rb +105 -0
- data/lib/vx/lib/consumer/subscriber.rb +84 -0
- data/lib/vx/lib/consumer/testing.rb +49 -0
- data/lib/vx/lib/consumer/version.rb +7 -0
- data/lib/vx/lib/consumer.rb +138 -0
- data/spec/lib/consumer_spec.rb +210 -0
- data/spec/lib/rpc_spec.rb +67 -0
- data/spec/lib/serializer_spec.rb +43 -0
- data/spec/lib/session_spec.rb +29 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/beefcake_test_message.rb +8 -0
- data/spec/support/test_consumers.rb +59 -0
- data/vx-lib-consumer.gemspec +27 -0
- metadata +150 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0f2ade54a8f310c3b4f0548782bb2855117fdc3d
|
4
|
+
data.tar.gz: ca1b073eafd155833ae8ff603b425c369f0de5d6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9f33d873c63cac1ea0c77cb3f0a8713cabd538fb52159965160dfa4d14069307d0ba0afc5b6ca22ac825d255f03c9c8bd060b280a5391ea6e4b3a3b717c0726e
|
7
|
+
data.tar.gz: eb06863cb980158841c173b42e52122f1606e9270f816f6968320473b78e678b602468b6c84653869ac9f7c5d694698b2a7502ecf9c3f39bccdcfefa208380b3
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Dmitry Galinsky
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Vx::Lib::Consumer
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'vx-lib-consumer'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install vx-consumer
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( http://github.com/<my-github-username>/vx-consumer/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
30
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Vx
|
2
|
+
module Lib
|
3
|
+
module Consumer
|
4
|
+
module Ack
|
5
|
+
|
6
|
+
def ack(multiple = false)
|
7
|
+
instrumentation = {
|
8
|
+
consumer: self.class.params.consumer_name,
|
9
|
+
properties: properties,
|
10
|
+
multiple: multiple,
|
11
|
+
channel: _channel.id
|
12
|
+
}
|
13
|
+
if _channel.open?
|
14
|
+
_channel.ack delivery_info.delivery_tag, multiple
|
15
|
+
instrument("ack", instrumentation)
|
16
|
+
true
|
17
|
+
else
|
18
|
+
instrument("ack_failed", instrumentation)
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def nack(multiple = false, requeue = false)
|
24
|
+
instrumentation = {
|
25
|
+
consumer: self.class.params.consumer_name,
|
26
|
+
properties: properties,
|
27
|
+
multiple: multiple,
|
28
|
+
requeue: requeue,
|
29
|
+
channel: channel.id
|
30
|
+
}
|
31
|
+
if _channel.open?
|
32
|
+
_channel.ack delivery_info.delivery_tag, multiple, requeue
|
33
|
+
instrument("nack", instrumentation)
|
34
|
+
true
|
35
|
+
else
|
36
|
+
instrument("nack_failed", instrumentation)
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'vx/common/rack/builder'
|
2
|
+
|
3
|
+
module Vx
|
4
|
+
module Lib
|
5
|
+
module Consumer
|
6
|
+
class Configuration
|
7
|
+
|
8
|
+
DEBUG = 'VX_CONSUMER_DEBUG'.freeze
|
9
|
+
|
10
|
+
|
11
|
+
attr_accessor :default_exchange_options, :default_queue_options,
|
12
|
+
:default_publish_options, :default_exchange_type, :pool_timeout,
|
13
|
+
:heartbeat, :spawn_attempts, :content_type, :instrumenter, :debug,
|
14
|
+
:on_error, :builders, :prefetch
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
reset!
|
18
|
+
end
|
19
|
+
|
20
|
+
def debug?
|
21
|
+
@debug ||= ENV[DEBUG]
|
22
|
+
end
|
23
|
+
|
24
|
+
def use(target, middleware, *args)
|
25
|
+
@builders[target].use middleware, *args
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_error(&block)
|
29
|
+
@on_error = block if block
|
30
|
+
@on_error
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset!
|
34
|
+
@default_exchange_type = :topic
|
35
|
+
@pool_timeout = 0.5
|
36
|
+
@heartbeat = :server
|
37
|
+
|
38
|
+
@spawn_attempts = 1
|
39
|
+
|
40
|
+
@content_type = 'application/json'
|
41
|
+
@prefetch = 1
|
42
|
+
|
43
|
+
@instrumenter = nil
|
44
|
+
@on_error = ->(e, env){ nil }
|
45
|
+
|
46
|
+
@builders = {
|
47
|
+
pub: Vx::Common::Rack::Builder.new,
|
48
|
+
sub: Vx::Common::Rack::Builder.new
|
49
|
+
}
|
50
|
+
|
51
|
+
@default_exchange_options = {
|
52
|
+
durable: true,
|
53
|
+
auto_delete: false
|
54
|
+
}
|
55
|
+
|
56
|
+
@default_queue_options = {
|
57
|
+
durable: true,
|
58
|
+
auto_delete: false,
|
59
|
+
exclusive: false
|
60
|
+
}
|
61
|
+
|
62
|
+
@default_publish_options = {
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Vx
|
2
|
+
module Lib
|
3
|
+
module Consumer
|
4
|
+
module Instrument
|
5
|
+
|
6
|
+
def instrument(name, payload, &block)
|
7
|
+
name = "#{name}.consumer.vx".freeze
|
8
|
+
|
9
|
+
if Consumer.configuration.debug?
|
10
|
+
$stdout.puts " --> #{name}: #{payload}"
|
11
|
+
end
|
12
|
+
|
13
|
+
if Consumer.configuration.instrumenter
|
14
|
+
Consumer.configuration.instrumenter.instrument(name, payload, &block)
|
15
|
+
else
|
16
|
+
begin
|
17
|
+
yield if block_given?
|
18
|
+
rescue Exception => e
|
19
|
+
Consumer.handle_exception(e, {})
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Vx
|
2
|
+
module Lib
|
3
|
+
module Consumer
|
4
|
+
Params = Struct.new(:consumer_class) do
|
5
|
+
|
6
|
+
attr_accessor :exchange_name, :exchange_options
|
7
|
+
attr_accessor :queue_name, :queue_options
|
8
|
+
attr_accessor :routing_key, :headers
|
9
|
+
attr_accessor :content_type
|
10
|
+
attr_accessor :ack
|
11
|
+
attr_accessor :exchange_type
|
12
|
+
attr_accessor :model
|
13
|
+
|
14
|
+
def exchange_name
|
15
|
+
@exchange_name || default_exchange_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def queue_name
|
19
|
+
@queue_name || ""
|
20
|
+
end
|
21
|
+
|
22
|
+
def ack
|
23
|
+
!!@ack
|
24
|
+
end
|
25
|
+
|
26
|
+
def content_type
|
27
|
+
@content_type || config.content_type
|
28
|
+
end
|
29
|
+
|
30
|
+
def exchange_type
|
31
|
+
@exchange_type || config.default_exchange_type
|
32
|
+
end
|
33
|
+
|
34
|
+
def exchange_options
|
35
|
+
(@exchange_options || config.default_exchange_options).merge(type: exchange_type)
|
36
|
+
end
|
37
|
+
|
38
|
+
def queue_options
|
39
|
+
@queue_options || config.default_queue_options
|
40
|
+
end
|
41
|
+
|
42
|
+
def publish_options
|
43
|
+
config.default_publish_options
|
44
|
+
end
|
45
|
+
|
46
|
+
def bind_options
|
47
|
+
opts = { }
|
48
|
+
opts.merge!(routing_key: routing_key) if routing_key
|
49
|
+
opts.merge!(headers: headers) if headers
|
50
|
+
opts
|
51
|
+
end
|
52
|
+
|
53
|
+
def consumer_name
|
54
|
+
@consumer_name ||= consumer_class.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def consumer_id
|
58
|
+
@consumer_id ||=
|
59
|
+
consumer_name.gsub(/::/, '/').
|
60
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
61
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
62
|
+
tr("-", "_").
|
63
|
+
downcase.
|
64
|
+
gsub("_consumer$", '')
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def config
|
70
|
+
Consumer.configuration
|
71
|
+
end
|
72
|
+
|
73
|
+
def default_exchange_name
|
74
|
+
"amq.#{exchange_type}"
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Vx
|
4
|
+
module Lib
|
5
|
+
module Consumer
|
6
|
+
module Publish
|
7
|
+
|
8
|
+
def publish(payload, options = {})
|
9
|
+
session.open
|
10
|
+
|
11
|
+
options ||= {}
|
12
|
+
options[:routing_key] = params.routing_key if params.routing_key && !options.key?(:routing_key)
|
13
|
+
options[:headers] = params.headers if params.headers && !options.key?(:headers)
|
14
|
+
|
15
|
+
options[:content_type] ||= params.content_type || configuration.content_type
|
16
|
+
options[:message_id] ||= SecureRandom.uuid
|
17
|
+
|
18
|
+
name = params.exchange_name
|
19
|
+
|
20
|
+
instrumentation = {
|
21
|
+
payload: payload,
|
22
|
+
exchange: name,
|
23
|
+
consumer: params.consumer_name,
|
24
|
+
properties: options,
|
25
|
+
}
|
26
|
+
|
27
|
+
with_middlewares :pub, instrumentation do
|
28
|
+
session.with_pub_channel do |ch|
|
29
|
+
instrument(:process_publishing, instrumentation.merge(channel: ch.id)) do
|
30
|
+
encoded = encode_payload(payload, options[:content_type])
|
31
|
+
x = session.declare_exchange ch, name, params.exchange_options
|
32
|
+
x.publish encoded, options
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def encode_payload(payload, content_type)
|
41
|
+
Serializer.pack(content_type, payload)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'thread'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Vx
|
6
|
+
module Lib
|
7
|
+
module Consumer
|
8
|
+
module Rpc
|
9
|
+
|
10
|
+
RPC_EXCHANGE_NAME = "".freeze
|
11
|
+
JSON_CONTENT_TYPE = 'application/json'.freeze
|
12
|
+
RPC_PAYLOAD_METHOD = 'method'.freeze
|
13
|
+
RPC_PAYLOAD_PARAMS = 'params'.freeze
|
14
|
+
RPC_PAYLOAD_RESULT = 'result'.freeze
|
15
|
+
|
16
|
+
class RpcProxy
|
17
|
+
|
18
|
+
attr_reader :client
|
19
|
+
|
20
|
+
def initialize(consumer)
|
21
|
+
@parent = consumer
|
22
|
+
@client = RpcClient.new consumer
|
23
|
+
@methods = {}
|
24
|
+
@defined = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(method, args, options = {})
|
28
|
+
ns = @parent.params.consumer_id
|
29
|
+
@client.call ns, method, args, options
|
30
|
+
end
|
31
|
+
|
32
|
+
def define
|
33
|
+
@parent.exchange RPC_EXCHANGE_NAME
|
34
|
+
@parent.queue "vx.rpc.#{@parent.params.consumer_id}".freeze, durable: false, auto_delete: true
|
35
|
+
|
36
|
+
@parent.send :define_method, :perform do |payload|
|
37
|
+
self.class.rpc.process_payload(properties, payload)
|
38
|
+
end
|
39
|
+
|
40
|
+
@defined = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def action(name, fn)
|
44
|
+
define unless @defined
|
45
|
+
@methods[name.to_s] = fn
|
46
|
+
end
|
47
|
+
|
48
|
+
def process_payload(properties, payload)
|
49
|
+
m = payload[RPC_PAYLOAD_METHOD]
|
50
|
+
p = payload[RPC_PAYLOAD_PARAMS]
|
51
|
+
|
52
|
+
if fn = @methods[m]
|
53
|
+
re = fn.call(*p)
|
54
|
+
@parent.publish(
|
55
|
+
{ 'result' => re },
|
56
|
+
routing_key: properties[:reply_to],
|
57
|
+
correlation_id: properties[:correlation_id],
|
58
|
+
content_type: JSON_CONTENT_TYPE
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class RpcClient
|
65
|
+
|
66
|
+
REP = "rep".freeze
|
67
|
+
REQ = "req".freeze
|
68
|
+
|
69
|
+
attr_reader :consumer
|
70
|
+
|
71
|
+
def initialize(consumer)
|
72
|
+
@consumer = consumer
|
73
|
+
@consumed = false
|
74
|
+
@await = {}
|
75
|
+
@mutex = Mutex.new
|
76
|
+
@wakeup = Mutex.new
|
77
|
+
end
|
78
|
+
|
79
|
+
def consume
|
80
|
+
return if @consumed
|
81
|
+
|
82
|
+
ch = consumer.session.conn.create_channel
|
83
|
+
consumer.session.assign_error_handlers_to_channel(ch)
|
84
|
+
|
85
|
+
@q = ch.queue(RPC_EXCHANGE_NAME, exclusive: true)
|
86
|
+
|
87
|
+
@subscriber =
|
88
|
+
@q.subscribe do |delivery_info, properties, payload|
|
89
|
+
handle_delivery ch, properties, payload
|
90
|
+
end
|
91
|
+
@consumed = true
|
92
|
+
end
|
93
|
+
|
94
|
+
def handle_delivery(ch, properties, payload)
|
95
|
+
|
96
|
+
if payload
|
97
|
+
payload = ::JSON.parse(payload)
|
98
|
+
end
|
99
|
+
|
100
|
+
instrumentation = {
|
101
|
+
consumer: consumer.params.consumer_name,
|
102
|
+
queue: @q.name,
|
103
|
+
rpc: REP,
|
104
|
+
channel: ch.id,
|
105
|
+
payload: payload,
|
106
|
+
properties: properties
|
107
|
+
}
|
108
|
+
|
109
|
+
consumer.with_middlewares :sub, instrumentation do
|
110
|
+
consumer.instrument(:start_processing, instrumentation)
|
111
|
+
consumer.instrument(:process, instrumentation) do
|
112
|
+
call_id = properties[:correlation_id]
|
113
|
+
c = @mutex.synchronize{ @await.delete(call_id) }
|
114
|
+
if c
|
115
|
+
@mutex.synchronize do
|
116
|
+
@await[call_id] = [properties, payload]
|
117
|
+
end
|
118
|
+
@wakeup.synchronize do
|
119
|
+
c.signal
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def call(ns, method, params, options = {})
|
127
|
+
timeout = options[:timeout] || 3
|
128
|
+
routing_key = options[:routing_key] || "vx.rpc.#{ns}".freeze
|
129
|
+
call_id = SecureRandom.uuid
|
130
|
+
cond = ConditionVariable.new
|
131
|
+
result = nil
|
132
|
+
|
133
|
+
message = {
|
134
|
+
method: method.to_s,
|
135
|
+
params: params,
|
136
|
+
id: call_id
|
137
|
+
}
|
138
|
+
|
139
|
+
with_queue do |q|
|
140
|
+
|
141
|
+
consumer.session.with_pub_channel do |ch|
|
142
|
+
exch = ch.exchange RPC_EXCHANGE_NAME
|
143
|
+
|
144
|
+
instrumentation = {
|
145
|
+
payload: message,
|
146
|
+
rpc: REQ,
|
147
|
+
exchange: exch.name,
|
148
|
+
consumer: consumer.params.consumer_name,
|
149
|
+
properties: { routing_key: routing_key, correlation_id: call_id },
|
150
|
+
channel: ch.id
|
151
|
+
}
|
152
|
+
|
153
|
+
@mutex.synchronize { @await[call_id] = cond }
|
154
|
+
|
155
|
+
consumer.with_middlewares :pub, instrumentation do
|
156
|
+
consumer.instrument(:process_publishing, instrumentation) do
|
157
|
+
exch.publish(
|
158
|
+
message.to_json,
|
159
|
+
routing_key: routing_key,
|
160
|
+
correlation_id: call_id,
|
161
|
+
reply_to: q.name,
|
162
|
+
content_type: JSON_CONTENT_TYPE
|
163
|
+
)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
@wakeup.synchronize{
|
168
|
+
cond.wait(@wakeup, timeout)
|
169
|
+
}
|
170
|
+
@mutex.synchronize do
|
171
|
+
_, payload = @await.delete(call_id)
|
172
|
+
if payload
|
173
|
+
result = payload[RPC_PAYLOAD_RESULT]
|
174
|
+
else
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
result
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
def with_queue
|
186
|
+
consume
|
187
|
+
yield @q
|
188
|
+
end
|
189
|
+
|
190
|
+
def subscriber?
|
191
|
+
!!@subscriber
|
192
|
+
end
|
193
|
+
|
194
|
+
def cancel
|
195
|
+
instrumentation = {
|
196
|
+
consumer: consumer.params.consumer_name,
|
197
|
+
rpc: 'consume'
|
198
|
+
}
|
199
|
+
|
200
|
+
consumer.instrument('cancel_consumer', instrumentation)
|
201
|
+
|
202
|
+
if subscriber?
|
203
|
+
@subscriber.cancel
|
204
|
+
@subscriber = nil
|
205
|
+
@consumed = false
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def rpc
|
211
|
+
@rpc ||= RpcProxy.new(self)
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Vx
|
2
|
+
module Lib
|
3
|
+
module Consumer
|
4
|
+
class Serializer
|
5
|
+
@@types = {}
|
6
|
+
|
7
|
+
Type = Struct.new(:content_type) do
|
8
|
+
def pack(&block)
|
9
|
+
@pack = block if block_given?
|
10
|
+
@pack
|
11
|
+
end
|
12
|
+
|
13
|
+
def unpack(&block)
|
14
|
+
@unpack = block if block_given?
|
15
|
+
@unpack
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def types
|
21
|
+
@@types
|
22
|
+
end
|
23
|
+
|
24
|
+
def define(content_type, &block)
|
25
|
+
fmt = Type.new content_type
|
26
|
+
fmt.instance_eval(&block)
|
27
|
+
types.merge! content_type => fmt
|
28
|
+
end
|
29
|
+
|
30
|
+
def lookup(content_type)
|
31
|
+
types[content_type]
|
32
|
+
end
|
33
|
+
|
34
|
+
def pack(content_type, body)
|
35
|
+
if fmt = lookup(content_type)
|
36
|
+
fmt.pack.call(body)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def unpack(content_type, body, model)
|
41
|
+
if fmt = lookup(content_type)
|
42
|
+
fmt.unpack.call(body, model)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
define 'text/plain' do
|
48
|
+
pack do |body|
|
49
|
+
body.to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
unpack do |body, _|
|
53
|
+
body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
define 'application/json' do
|
58
|
+
pack do |body|
|
59
|
+
if body.is_a?(String)
|
60
|
+
body
|
61
|
+
else
|
62
|
+
::JSON.dump body
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
unpack do |payload, model|
|
67
|
+
if model && model.respond_to?(:from_json)
|
68
|
+
model.from_json payload
|
69
|
+
else
|
70
|
+
::JSON.parse(payload)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
define 'application/x-protobuf' do
|
76
|
+
|
77
|
+
pack do |object|
|
78
|
+
object.encode.to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
unpack do |payload, model|
|
82
|
+
raise ModelIsNotDefined unless model
|
83
|
+
model.decode payload
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|