thrift-amqp-ruby 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 47b60f6d88a19c39352bd0173213c238299e01c6
4
+ data.tar.gz: 96733bf92ae83b43a9da73b7a449f6c33c7e6aff
5
+ SHA512:
6
+ metadata.gz: 6b76ebc19ab8fe491c572f6d95fca291ea1f7592474c596ae7e3dcbf58b74b378ff766a0a7d9fa8fb8eace1fe6389a46ce75b8bf47b761a6ce191475b5241742
7
+ data.tar.gz: 651f7479ecc599907ded1610d25c8087281552ab747cb72601a637128c88f037b861eb83876e55d334e8fd582fe194869fc1a2ea4f2855bbd816f7a59ec84536
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in thrift-amqp-ruby.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Alexis Montagne
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,29 @@
1
+ # Thrift::Amqp::Ruby
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'thrift-amqp-ruby'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install thrift-amqp-ruby
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/thrift-amqp-ruby/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 a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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, :durable => true)
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.1"
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
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'thrift/amqp/ruby/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "thrift-amqp-ruby"
8
+ spec.version = Thrift::Amqp::Ruby::VERSION
9
+ spec.authors = ["Alexis Montagne"]
10
+ spec.email = ["alexis.montagne@gmail.com"]
11
+ spec.summary = %q{Thrift transport layer over AMQP}
12
+ spec.description = %q{Thrift transport layer over AMQP}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_dependency "bunny"
24
+ spec.add_dependency "thrift"
25
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thrift-amqp-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alexis Montagne
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bunny
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thrift
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Thrift transport layer over AMQP
70
+ email:
71
+ - alexis.montagne@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - lib/thrift/amqp/amqp_rpc_client.rb
82
+ - lib/thrift/amqp/amqp_rpc_service.rb
83
+ - lib/thrift/amqp/ruby.rb
84
+ - lib/thrift/amqp/ruby/version.rb
85
+ - thrift-amqp-ruby.gemspec
86
+ homepage: ''
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.2.2
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Thrift transport layer over AMQP
110
+ test_files: []