thrift-amqp 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 654ead61ea5b16a491ecb1cee0b2a79151fc0c95
4
- data.tar.gz: a7e3da74a7af5f15420f2e60c6817353948a09e1
3
+ metadata.gz: c52372806c20e8d70a228cf79074b45d4f71cec3
4
+ data.tar.gz: 42cde22f78924bab6d241179faf0c7e27f96e710
5
5
  SHA512:
6
- metadata.gz: c34dc87542445c294ecc799704820704166dcfef58ffc39d95e8b8c7b43d2b014437c65c1ce8956db3a48edb0e1bc0d5624c42f90f45357be8c7666e6d2e908c
7
- data.tar.gz: de834e7e6a1f99d2404330ee5033c756e45f09e13f5c8059691f3e5898d21777dc1c2ee8faff2235154eb976424e3c3ff56589b9a6bcfec2631ab412bd82fe44
6
+ metadata.gz: 7d4f43ec13ef61f936ea8a0f9a9ce14ae1e8b3227bdc93d7634ec59ec010871066d057ab643f03f4e542ec74ca02bba2ec9a1b36302fe866e6a8b254f77d3960
7
+ data.tar.gz: 265a92cf5c0cba2738ffcb6c678e9412c2441f6940d81faea7944f094ec8a23ccce4f8e74cdc6167a57c4c8ad7407ddad1a73f4643c19a6a8f9cae4464daf7be
@@ -0,0 +1,165 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+ #
19
+
20
+ require 'thrift'
21
+ require 'bunny'
22
+ require 'stringio'
23
+ require 'timeout'
24
+ require 'uuidtools'
25
+
26
+ module Thrift
27
+
28
+ class ResponseTimeout < Timeout::Error; end
29
+
30
+ class AmqpRpcClientTransport < BaseTransport
31
+
32
+ def initialize(service_queue_name, opts={})
33
+ @service_queue_name = service_queue_name
34
+ @outbuf = Bytes.empty_byte_buffer
35
+
36
+ if opts[:connection].nil?
37
+ if opts[:host].nil?
38
+ raise ArgumentError, ":host key not provided in opts dict to make connection"
39
+ end
40
+
41
+ if opts[:port].nil?
42
+ raise ArgumentError, ":port key not provided in opts dict to make connection"
43
+ end
44
+
45
+ vhost = opts[:vhost] || "/"
46
+ user = opts[:user] || "guest"
47
+ password = opts[:password] || "guest"
48
+ ssl = opts[:ssl] || false
49
+
50
+ @conn = Bunny.new(:host => opts[:host], :port => opts[:port], :vhost => vhost, :user => user, :password => password, :ssl=> ssl)
51
+
52
+ @conn.start
53
+ @connection_started = true
54
+ else
55
+ @conn = opts[:connection]
56
+ @connection_started = false
57
+ end
58
+
59
+ @from_name = opts[:from_name].nil? ? "Unknown Client" : opts[:from_name]
60
+ @exchange = opts[:exchange] || nil
61
+
62
+ @ch = @conn.create_channel
63
+ @service_exchange = @exchange.nil? ? @ch.default_exchange : @ch.direct(@exchange)
64
+ @service_response_exchange = @ch.default_exchange
65
+ @reply_queue = @ch.queue("", :exclusive => true)
66
+ @is_opened = true
67
+
68
+ end
69
+
70
+ def close
71
+ if @is_opened
72
+ @reply_queue.delete
73
+ @ch.close
74
+
75
+ if @connection_started
76
+ @conn.close
77
+ @connection_started = false
78
+ end
79
+
80
+ @is_opened = false
81
+ end
82
+ end
83
+
84
+ def open?; @is_opened end
85
+ def read(sz); @inbuf.read sz end
86
+ def write(buf); @outbuf << Bytes.force_binary_encoding(buf) end
87
+
88
+ #If blocking is set to true then wait for a response message in the reply_to queue, otherwise
89
+ #just send and go!
90
+ def flush(options={})
91
+
92
+ operation = options.has_key?(:operation) ? options[:operation] : ""
93
+ blocking = options.has_key?(:blocking) ? options[:blocking] : true
94
+ msg_timeout = options.has_key?(:msg_timeout) ? options[:msg_timeout] : 10
95
+ log_messages = options.has_key?(:log_messages) ? options[:log_messages] : false
96
+
97
+ correlation_id = self.generate_uuid
98
+
99
+ headers = {:service_name => @service_queue_name,
100
+ :operation => operation,
101
+ :response_required => blocking, #Tell the receiver if a response is required
102
+ :from_name => @from_name
103
+ }
104
+
105
+ #Publish the message
106
+ print_log "Publishing message reply-to: #{@reply_queue.name} - headers: #{headers}", correlation_id if log_messages
107
+ start_time = Time.now
108
+ @service_exchange.publish(@outbuf,
109
+ :routing_key => @service_queue_name,
110
+ :correlation_id => correlation_id,
111
+ :expiration => msg_timeout,
112
+ :reply_to => @reply_queue.name,
113
+ :headers => headers)
114
+
115
+ #If this is a standard RPC blocking call, then wait for there to be a response from the
116
+ #service provider or timeout and log the timeout
117
+ if blocking
118
+ @response = ""
119
+ begin
120
+ #Adding 1sec to timeout to account for clock differences
121
+ Timeout.timeout(msg_timeout + 1, ResponseTimeout) do
122
+ @reply_queue.subscribe(:block => true) do |delivery_info, properties, payload|
123
+
124
+ if log_messages
125
+ response_time = Time.now - start_time
126
+ print_log "---- Response Message received in #{response_time}sec for #{@reply_queue.name}", correlation_id
127
+ print_log "HEADERS: #{properties}", correlation_id
128
+ end
129
+
130
+ if properties[:correlation_id] == correlation_id
131
+ @response = payload
132
+
133
+ #once the return message has been received, no need to continue a subscription
134
+ delivery_info.consumer.cancel
135
+ end
136
+ end
137
+ end
138
+ rescue ResponseTimeout => ex
139
+ #Trying to work around weirdness being seen in a multi threaded workflow environment
140
+ if @response == ""
141
+ msg = "A timeout has occurred (#{msg_timeout}sec) trying to call #{@service_queue_name}.#{operation}"
142
+ print_log msg, correlation_id
143
+ raise ex, msg
144
+ else
145
+ print_log "Ignoring timeout - #{@response}", correlation_id
146
+ end
147
+ end
148
+ @inbuf = StringIO.new Bytes.force_binary_encoding(@response)
149
+ end
150
+ @outbuf = Bytes.empty_byte_buffer
151
+ end
152
+
153
+ protected
154
+
155
+ def generate_uuid
156
+ UUIDTools::UUID.timestamp_create.to_s
157
+ end
158
+
159
+ def print_log(message="", correlation_id="")
160
+ puts "#{Time.now.utc} C Thread: #{Thread.current.object_id} CID:#{correlation_id} - #{message}"
161
+ end
162
+ end
163
+ end
164
+
165
+
@@ -0,0 +1,165 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+ #
19
+
20
+ require 'bunny'
21
+ require 'thrift'
22
+
23
+ module Thrift
24
+ class AmqpRpcServer < BaseServer
25
+
26
+ class ProcessingTimeout < Timeout::Error; end
27
+
28
+ def initialize(processor, opts={})
29
+
30
+ @processor = processor
31
+
32
+ if opts[:connection].nil?
33
+
34
+ if opts[:host].nil?
35
+ raise ArgumentError, ":host key not provided in opts dict to make connection"
36
+ end
37
+
38
+ if opts[:port].nil?
39
+ raise ArgumentError, ":port key not provided in opts dict to make connection"
40
+ end
41
+
42
+ vhost = opts[:vhost] || "/"
43
+ user = opts[:user] || "guest"
44
+ password = opts[:password] || "guest"
45
+ ssl = opts[:ssl] || false
46
+
47
+ @conn = Bunny.new(:host => opts[:host], :port => opts[:port], :vhost => vhost, :user => user, :password => password, :ssl=> ssl)
48
+ @conn.start
49
+ else
50
+ @conn = opts[:connection]
51
+ end
52
+
53
+ #print "service:", @conn, "\n"
54
+
55
+ if not opts.has_key?(:queue_name)
56
+ raise ArgumentError, "A service queue name has not been specified"
57
+ end
58
+
59
+ @queue_name = opts[:queue_name]
60
+ @protocol_factory = opts[:protocol_factory] || BinaryProtocolFactory
61
+ @exchange = opts[:exchange] || nil
62
+
63
+ end
64
+
65
+ def close
66
+
67
+ if not @request_channel.nil? and @request_channel.respond_to?('close')
68
+ @request_channel.close
69
+ end
70
+
71
+ #Always close the broker connection when closing the server
72
+ @conn.close
73
+
74
+ end
75
+
76
+
77
+
78
+ def serve(options={})
79
+ log_messages = options[:log_messages] || false
80
+ max_messages = options[:max_messages].nil? ? 10 : options[:max_messages]
81
+ response_timeout = options[:response_timeout] || 10
82
+
83
+ #Create a channel to the service queue
84
+ @request_channel = @conn.create_channel(nil, max_messages )
85
+
86
+ if @exchange.nil?
87
+ @service_exchange = @request_channel.default_exchange
88
+ @request_queue = @request_channel.queue(@queue_name, :auto_delete => true)
89
+ else
90
+ @service_exchange = @request_channel.direct(@exchange,:durable => true)
91
+ @request_queue = @request_channel.queue(@queue_name, :auto_delete => true).bind(@service_exchange, :routing_key => @queue_name)
92
+ end
93
+
94
+ @request_queue.subscribe(:block => true) do |delivery_info, properties, payload|
95
+
96
+ if log_messages
97
+ Thread.current["correlation_id"] = properties.correlation_id
98
+ print_log "---- Message received ----"
99
+ print_log "HEADERS: #{properties}"
100
+ end
101
+
102
+ Thread.current["correlation_id"] = properties.correlation_id
103
+
104
+ response_channel = @conn.create_channel
105
+ response_exchange = response_channel.default_exchange
106
+
107
+ response_required = properties.headers.has_key?('response_required') ? properties.headers['response_required'] : true
108
+ process_timeout = response_timeout.to_i > properties.expiration.to_i ? response_timeout.to_i : properties.expiration.to_i
109
+
110
+ #Binary content will imply thrift based message payload
111
+ if properties.content_type == 'application/octet-stream'
112
+
113
+ print_log "Request to process #{@queue_name}.#{properties.headers['operation']} in #{process_timeout}sec" if log_messages
114
+
115
+ input = StringIO.new payload
116
+ out = StringIO.new
117
+ transport = IOStreamTransport.new input, out
118
+ protocol = @protocol_factory.new.get_protocol transport
119
+
120
+ begin
121
+ start_time = Time.now
122
+ Timeout.timeout(process_timeout, ProcessingTimeout) do
123
+ @processor.process protocol, protocol
124
+ end
125
+ processing_time = Time.now - start_time
126
+
127
+ #rewind the buffer for reading
128
+ if out.length > 0
129
+ out.rewind
130
+
131
+ print_log "Time to process request: #{processing_time}sec Response length: #{out.length}" if log_messages
132
+
133
+ if response_required
134
+ response_exchange.publish(out.read(out.length),
135
+ :routing_key => properties.reply_to,
136
+ :correlation_id => properties.correlation_id,
137
+ :content_type => 'application/octet-stream' )
138
+ end
139
+ end
140
+
141
+ rescue ProcessingTimeout => ex
142
+ print_log "A timeout has occurred (#{process_timeout}sec) trying to call #{@queue_name}.#{properties.headers['operation']}"
143
+ end
144
+
145
+ else
146
+
147
+ print_log "Unable to process message content of type #{properties.content_type}. The message will be rejected"
148
+ @request_channel.reject(delivery_info.delivery_tag, false)
149
+
150
+ end
151
+
152
+ response_channel.close
153
+
154
+
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ def print_log(message="")
161
+ puts "#{Time.now.utc} S Thread: #{Thread.current.object_id} CID:#{Thread.current["correlation_id"]} - #{message}"
162
+
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,7 @@
1
+ module Thrift
2
+ module Amqp
3
+ module Ruby
4
+ VERSION = "0.0.2"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require "thrift/amqp/ruby/version"
2
+ require "thrift/amqp/amqp_rpc_client"
3
+ require "thrift/amqp/amqp_rpc_service"
4
+
5
+ module Thrift
6
+ module Amqp
7
+ module Ruby
8
+ end
9
+ end
10
+ end
@@ -1,10 +1,13 @@
1
1
  require 'bunny'
2
2
  require 'thrift'
3
+ require 'logger'
4
+
5
+ LOGGER = Logger.new(STDOUT)
6
+ LOGGER.level = Logger::INFO
3
7
 
4
8
  module Thrift
5
9
  class AMQPServer < BaseServer
6
- def initialize(processor, iprot_factory, oprot_factory=nil, opts={})
7
-
10
+ def initialize(processor, iprot_factory, oprot_factory = nil, opts = {})
8
11
  @processor = processor
9
12
  @iprot_factory = iprot_factory
10
13
  @oprot_factory = oprot_factory || iprot_factory
@@ -28,22 +31,19 @@ module Thrift
28
31
 
29
32
  @channel.prefetch @prefetch
30
33
 
31
- queue.subscribe(block: false) do |delivery_info, properties, payload|
34
+ queue.subscribe(
35
+ manual_ack: true,
36
+ block: true
37
+ ) do |delivery_info, _properties, payload|
32
38
  trans = MemoryBufferTransport.new(payload)
33
39
  iprot = @iprot_factory.get_protocol(trans)
34
40
 
35
- @processor.process(iprot, nil)
36
-
37
- @channel.acknowledge(delivery_info.delivery_tag, false)
38
- end
39
-
40
- loop do
41
- sleep 5
42
-
43
- if @channel.open?
44
- @channel.close
45
- @channel.open
41
+ begin
42
+ @processor.process(iprot, nil)
43
+ rescue => e
44
+ LOGGER.error("Processor failure #{e}")
46
45
  end
46
+ @channel.acknowledge(delivery_info.delivery_tag, false)
47
47
  end
48
48
  end
49
49
  end
@@ -1,5 +1,5 @@
1
1
  module Thrift
2
2
  module AMQP
3
- VERSION = "0.0.3"
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thrift-amqp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Montagne
@@ -94,7 +94,11 @@ files:
94
94
  - README.md
95
95
  - Rakefile
96
96
  - lib/thrift/amqp.rb
97
+ - lib/thrift/amqp/amqp_rpc_client.rb
98
+ - lib/thrift/amqp/amqp_rpc_service.rb
97
99
  - lib/thrift/amqp/client.rb
100
+ - lib/thrift/amqp/ruby.rb
101
+ - lib/thrift/amqp/ruby/version.rb
98
102
  - lib/thrift/amqp/server.rb
99
103
  - lib/thrift/amqp/version.rb
100
104
  - thrift-amqp.gemspec
@@ -118,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
122
  version: '0'
119
123
  requirements: []
120
124
  rubyforge_project:
121
- rubygems_version: 2.2.2
125
+ rubygems_version: 2.4.6
122
126
  signing_key:
123
127
  specification_version: 4
124
128
  summary: Thrift transport layer over AMQP