thrifter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fd99b7900e55cc7abcfe145198cb184b4dd27d28
4
+ data.tar.gz: b5aeb9adef656c411b43327a619496f0dd46b429
5
+ SHA512:
6
+ metadata.gz: 30d3d385ef740047a5dae0b0f704518a5e9c77e551bd29d7cf991a65b33fb5255a4b246571e6088ae36a457bb5ca37a9d90dda2c949efd50f3bfcb7431ce09be
7
+ data.tar.gz: c0c644e11697991a4f2187dd505906a53de4829d74f7653b7f65ef25965ddf6649a78caca0f5d5e05a64ab9385d805f6c981a6cadbc380c43b898391f73711e0
@@ -0,0 +1,3 @@
1
+ .vagrant
2
+ .git
3
+ tmp
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
@@ -0,0 +1,15 @@
1
+ FROM ruby:2.1
2
+
3
+ RUN mkdir -p /app/lib/thrifter
4
+
5
+ WORKDIR /app
6
+
7
+ # Add only the files that are required to run bundle installer.
8
+ # Remaining library code will be provided via a volume
9
+ ADD Gemfile /app/
10
+ ADD thrifter.gemspec /app/
11
+ ADD lib/thrifter/version.rb /app/lib/thrifter/
12
+
13
+ RUN bundle install -j $(nproc)
14
+
15
+ CMD [ "bundle", "console" ]
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in thrifter.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 ahawkins
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.
@@ -0,0 +1,57 @@
1
+ THRIFT:=vendor/gen-rb/test_types.rb
2
+
3
+ BUNDLE:=tmp/bundle
4
+ DOCKER_IMAGES:=tmp/docker_images
5
+
6
+ BUNDLE_IMAGE:=thrifter/bundle
7
+
8
+ # CircleCI does not support --rm, so if the environment variable has
9
+ # value, then don't include --rm.
10
+ ifneq ($(shell echo $$CIRCLECI),)
11
+ DOCKER_RUN:=docker run -it
12
+ else
13
+ DOCKER_RUN:=docker run --rm -it
14
+ endif
15
+
16
+ $(THRIFT): test.thrift
17
+ mkdir -p $(@D)
18
+ thrift -o vendor --gen rb test.thrift
19
+
20
+ $(DOCKER_IMAGES):
21
+ mkdir -p $(@D)
22
+ touch $@
23
+
24
+ $(BUNDLE): Dockerfile thrifter.gemspec $(DOCKER_IMAGES)
25
+ docker build -t $(BUNDLE_IMAGE) .
26
+ docker inspect -f '{{ .Id }}' $(BUNDLE_IMAGE) >> $(DOCKER_IMAGES)
27
+
28
+ .PHONY: test
29
+ # For quick and easy access to the most common thing
30
+ test: test-lib
31
+
32
+ .PHONY: test-lib
33
+ test-lib: $(THRIFT) $(BUNDLE)
34
+ $(DOCKER_RUN) -v $(CURDIR):/app $(BUNDLE_IMAGE) bundle exec rake test
35
+
36
+ .PHONY: test-ci
37
+ test-ci: test-lib test-monkey
38
+
39
+ .PHONY: test-monkey
40
+ test-monkey: teardown
41
+ docker run -d -v $(CURDIR):/app --name server $(BUNDLE_IMAGE) script/server
42
+ $(DOCKER_RUN) --link server:server -v $(CURDIR):/app \
43
+ $(BUNDLE_IMAGE) script/monkey-client server:9090
44
+
45
+ .PHONY:
46
+ release:
47
+ bundle exec rake release
48
+
49
+ .PHONY: treadown
50
+ teardown:
51
+ -docker stop server 2> /dev/null
52
+ -docker rm server 2> /dev/null
53
+
54
+ .PHONY: clean
55
+ clean: $(DOCKER_IMAGES) teardown
56
+ -sort -u $(DOCKER_IMAGES) | xargs --no-run-if-empty docker rmi 2> /dev/null
57
+ rm -f $(DOCKER_IMAGES) $(APP)
@@ -0,0 +1,283 @@
1
+ # Thrifter
2
+
3
+ Thrifter addresses the shortcoming in the official library for
4
+ production uses. It's most important features are:
5
+
6
+ * Thread safe via connection pool
7
+ * Safe for use in long running processes
8
+ * Simple RPC queuing
9
+ * Retry support
10
+ * Proper timeouts by default
11
+ * Metrics
12
+ * Better error handling
13
+ * Middleware
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'thrifter'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install thrifter
30
+
31
+ ## Usage
32
+
33
+ `Thrifer` is a factory (similar to `DelegateClass`) for building
34
+ client classes. It can be used like `DelegateClass` or like `Struct`.
35
+ The result is subclasses of `Thrifter::Client`. The classes use the
36
+ same methods to make RPCs. For example if the thrift service has a
37
+ `fooBar` RPC, the generated class has a `fooBar` method to invoke the
38
+ RPC. Here are some examples.
39
+
40
+ ```ruby
41
+ # Struct style
42
+ ServiceClient = Thrifter.build(MyService::Client)
43
+
44
+ The struct style take a block as well
45
+ ServiceClient = Thrifer.build(MyService::Client) do
46
+ def custom_method(args)
47
+ # something something
48
+ end
49
+ end
50
+
51
+ # Delegate class style (easiest to add abstractions)
52
+ class MyServiceClient < Thrifter.build(MyService::Client)
53
+ def custom_method(args)
54
+ # something something
55
+ end
56
+ end
57
+ ```
58
+
59
+ ### Configuration
60
+
61
+ `Thrifter` uses a configuration object on each class to track
62
+ dependent objects. Configured objects are injected into each instance.
63
+ This makes it easy to configure classes in different environment (e.g.
64
+ production vs test). All settings are documented below. `uri` is the
65
+ most important! It must be set to instantiate clients.
66
+
67
+ ```ruby
68
+ class MyClient < Thrifer.build(MyService::Client)
69
+ # Thrift specific things
70
+ config.transport = Thrift::FramedTransport
71
+ config.protocol = Thrift::BinaryTransport
72
+
73
+ # Pool settings
74
+ config.pool_size = 12
75
+ config.pool_timeout = 0.15
76
+
77
+ # Network Settings
78
+ config.rpc_timeout = 0.15
79
+
80
+ # Required to instantiate the client!
81
+ config.uri = 'tcp://foo:2383'
82
+ end
83
+
84
+ #The common block form is supported as well
85
+ MyClient.configure do |config|
86
+ # ... same as above
87
+ end
88
+ ```
89
+
90
+ ### RPC Metrics with Statsd
91
+
92
+ Statsd metrics are **opt-in**. By default, `Thrifter` sets the statsd
93
+ client to a null implementation. If you want metrics, set
94
+ `config.statsd` to an object that implements the [statsd-ruby][]
95
+ interface. `Thrifter` emits the following metrics:
96
+
97
+ * time on each rpc calls
98
+ * number of `Thrift::TransportException`
99
+ * number of `Thrift::ProtocolExeption`
100
+ * number of `Thrift::ApplicationExeption`
101
+ * number of `Timeout::Error`
102
+ * number of generic errors (e.g. none of the above known errors)
103
+
104
+ It's recommended that the `statsd` object do namespacing
105
+ (statsd-ruby has it built in). This ensures client metrics don't
106
+ get intermingled with wider application metrics. Here's an example:
107
+
108
+ ```ruby
109
+ ServiceClient = Thrifter.build(MyService::Client)
110
+ # Now in production.rb
111
+ ServiceClient.config.statsd = Statsd.new namespace: 'my_service'
112
+ ```
113
+
114
+ ### RPC Queuing
115
+
116
+ Certain systems may need to queue RPCs to other systems. This is only
117
+ useful for `void` RPCs or for when an outside system may be flaky.
118
+ Assume `MyService` has a `logStats` RPC. Your application is producing
119
+ stats that should make it upstream, but there are intermitent network
120
+ problems effeciting stats collection. Include `Thrift::Queueing` and
121
+ any RPC will automatically be sent to sidekiq for eventual processing.
122
+
123
+ ```ruby
124
+ class ServiceClient < Thrifter.build(MyService::Client)
125
+ include Thrifter::Queuing
126
+ end
127
+ ```
128
+
129
+ Now instances of `ServiceClient` now respond to `queued`. This returns
130
+ a queue based instance. All RPC methods will work as usual. Here's an
131
+ example:
132
+
133
+ ```ruby
134
+ # Assume client is an instance of ServiceClient
135
+ my_service.queued.logStats({ 'users' => 5 })
136
+
137
+ # Naturally the block form may be used as well
138
+ my_service.queued do |queue|
139
+ queue.logStats({ 'sessions' => 50 })
140
+ queue.logStats({ 'posts' => 30 })
141
+ end
142
+ ```
143
+
144
+ All RPCs will be sent to the `thrift` sidekiq queue. They will follow
145
+ default sidekiq retry backoff and the like.
146
+
147
+ ### RPC Retrying
148
+
149
+ Systems have syncrhonous RPCs. Unfortunately sometimes these don't
150
+ work for whatever reason. It's good practice to retry these RPCs
151
+ (within certain limits) if they don't succeed the first time.
152
+ `Thrift::Retriable` is perfect for this use case.
153
+
154
+ ```ruby
155
+ class ServiceClient < Thrifter.build(MyService::Client)
156
+ include Thrifter::Retriable
157
+ end
158
+ ```
159
+
160
+ ```ruby
161
+ # Assume client is an instance of ServiceClient
162
+
163
+ # logStats will be retried 3 times at 0.1 second intervals if any
164
+ # known thrift or network errors happen.
165
+ my_service.with_retry.logStats({ 'users' => 5 })
166
+
167
+ # These settings can be customized by the retriable method.
168
+ my_sevice.with_retry({tries: 10, delay: 0.3 }).logStats({ 'sessions' => 50 })
169
+
170
+ # Naturally the block form may be used as well
171
+ my_service.with_retry do |with_retry|
172
+ with_retry.logStats({ 'sessions' => 50 })
173
+ with_retry.logStats({ 'posts' => 30 })
174
+ end
175
+ ```
176
+
177
+ `Thrift::Retriable` is a simple retry solution for syncronous RPCs.
178
+ Look into something like [retriable][] if you want a more robust
179
+ solution for different use cases.
180
+
181
+ ### Middleware
182
+
183
+ The middleware approach is great for providing a flexible extension
184
+ points to hook into the RPC process. `Thrifter::Client` provides a
185
+ middleware implementation to common to many other ruby libraries.
186
+ Middleware can only be customized at the class level. This ensures
187
+ middleware applies when used in extensions.
188
+
189
+ ```ruby
190
+ class MyClient < Thrifter.build(MyService::Client)
191
+ use MyMiddlware
192
+ use MySecondMiddleware
193
+ end
194
+ ```
195
+
196
+ Since middleware must defined at the class level, you should defer
197
+ setting up middleware that depend on objects until process boot. For
198
+ example, if you have `LoggingMiddleware` and you need to log to
199
+ different places depending on environment, you should add the
200
+ middleware in whatever code configurres that environment. Only static
201
+ middleware should be configured directly in the class itself.
202
+
203
+ A middleware must implement the `call` method and accept at least one
204
+ argument to `initialize`. The `call` method recieves a `Thrifer::RPC`.
205
+ `Thrifter::RPC` is a sipmle struct with a `name` and `args` methods.
206
+ Here's an example:
207
+
208
+ ```ruby
209
+ class LoggingMiddleware
210
+ def initialize(app)
211
+ @app = app
212
+ end
213
+
214
+ def call(rpc)
215
+ puts "Running #{rp.name} with #{rpc.args.inspect}"
216
+ @app.call rpc
217
+ end
218
+ end
219
+ ```
220
+
221
+ ### Error Wrapping
222
+
223
+ A lot of things can go wrong in the thrift stack. This means the
224
+ caller may need to deal with a large amount of different exceptions.
225
+ For example, does it really matter if `Thrift::ProtocolException` or
226
+ `Thrift::TransportException` was raied? Can the caller recover from
227
+ either of them? No. So instead of allowing these semantics to
228
+ propogate up abstraction levels, it's better to encapsulate them in a
229
+ single error. This is easily implemented with a middleware and once is
230
+ included in the library. When this middleare is used, all known
231
+ networking & thrift exceptions will be raised as
232
+ `Thrifter::ClientError`.
233
+
234
+ ```ruby
235
+ class MyService < Thrifter.build(MyService::Client)
236
+ use Thrifter::ErrorWrapping
237
+ end
238
+ ```
239
+
240
+ A list of other known error classes can be provided to wrap more than
241
+ the library's known set.
242
+
243
+ ```ruby
244
+ class MyService < Thrifter.build(MyService::Client)
245
+ use Thrifter::ErrorWrapping, [ SomeErrorClass ]
246
+ end
247
+ ```
248
+
249
+ Note, `Thrifter` will still count individual errors as described in
250
+ the metrics section.
251
+
252
+ ### Pinging
253
+
254
+ Components in a system may need to inquire if other systems are
255
+ available before continuing. `Thrifer::Ping` is just that.
256
+ `Thrifter::Ping` assumes the service has a `ping` RPC. If your
257
+ service does not have one (or is named differently) simply implement
258
+ the `ping` method on the class. Any successful response will count as
259
+ up, anything else will not.
260
+
261
+ ```ruby
262
+ class MyService < Thrifer.build(MyService::Client)
263
+ include Thrifer::Ping
264
+
265
+ # Define a ping method if the service does not have one
266
+ def ping
267
+ my_other_rpc
268
+ end
269
+ end
270
+
271
+ # my_service.up? # => true
272
+ ```
273
+
274
+ ## Contributing
275
+
276
+ 1. Fork it ( https://github.com/saltside/thrifter/fork )
277
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
278
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
279
+ 4. Push to the branch (`git push origin my-new-feature`)
280
+ 5. Create a new Pull Request
281
+
282
+ [retriable]: https://github.com/kamui/retriable
283
+ [statsd-ruby]: https://github.com/reinh/statsd
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.test_files = Rake::FileList['test/**/*_test.rb']
6
+ end
7
+
8
+ task default: :test
@@ -0,0 +1,126 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
5
+ VAGRANTFILE_API_VERSION = "2"
6
+
7
+ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
8
+ # All Vagrant configuration is done here. The most common configuration
9
+ # options are documented and commented below. For a complete reference,
10
+ # please see the online documentation at vagrantup.com.
11
+
12
+ # Every Vagrant virtual environment requires a box to build off of.
13
+ config.vm.box = "ubuntu/trusty64"
14
+
15
+ config.vm.provision "docker" do |docker|
16
+ docker.pull_images "ruby:2.1"
17
+ end
18
+
19
+ # Disable automatic box update checking. If you disable this, then
20
+ # boxes will only be checked for updates when the user runs
21
+ # `vagrant box outdated`. This is not recommended.
22
+ # config.vm.box_check_update = false
23
+
24
+ # Create a forwarded port mapping which allows access to a specific port
25
+ # within the machine from a port on the host machine. In the example below,
26
+ # accessing "localhost:8080" will access port 80 on the guest machine.
27
+ # config.vm.network "forwarded_port", guest: 80, host: 8080
28
+
29
+ # Create a private network, which allows host-only access to the machine
30
+ # using a specific IP.
31
+ # config.vm.network "private_network", ip: "192.168.33.10"
32
+
33
+ # Create a public network, which generally matched to bridged network.
34
+ # Bridged networks make the machine appear as another physical device on
35
+ # your network.
36
+ # config.vm.network "public_network"
37
+
38
+ # If true, then any SSH connections made will enable agent forwarding.
39
+ # Default value: false
40
+ # config.ssh.forward_agent = true
41
+
42
+ # Share an additional folder to the guest VM. The first argument is
43
+ # the path on the host to the actual folder. The second argument is
44
+ # the path on the guest to mount the folder. And the optional third
45
+ # argument is a set of non-required options.
46
+ # config.vm.synced_folder "../data", "/vagrant_data"
47
+
48
+ # Provider-specific configuration so you can fine-tune various
49
+ # backing providers for Vagrant. These expose provider-specific options.
50
+ # Example for VirtualBox:
51
+ #
52
+ # config.vm.provider "virtualbox" do |vb|
53
+ # # Don't boot with headless mode
54
+ # vb.gui = true
55
+ #
56
+ # # Use VBoxManage to customize the VM. For example to change memory:
57
+ # vb.customize ["modifyvm", :id, "--memory", "1024"]
58
+ # end
59
+ #
60
+ # View the documentation for the provider you're using for more
61
+ # information on available options.
62
+
63
+ # Enable provisioning with CFEngine. CFEngine Community packages are
64
+ # automatically installed. For example, configure the host as a
65
+ # policy server and optionally a policy file to run:
66
+ #
67
+ # config.vm.provision "cfengine" do |cf|
68
+ # cf.am_policy_hub = true
69
+ # # cf.run_file = "motd.cf"
70
+ # end
71
+ #
72
+ # You can also configure and bootstrap a client to an existing
73
+ # policy server:
74
+ #
75
+ # config.vm.provision "cfengine" do |cf|
76
+ # cf.policy_server_address = "10.0.2.15"
77
+ # end
78
+
79
+ # Enable provisioning with Puppet stand alone. Puppet manifests
80
+ # are contained in a directory path relative to this Vagrantfile.
81
+ # You will need to create the manifests directory and a manifest in
82
+ # the file default.pp in the manifests_path directory.
83
+ #
84
+ # config.vm.provision "puppet" do |puppet|
85
+ # puppet.manifests_path = "manifests"
86
+ # puppet.manifest_file = "site.pp"
87
+ # end
88
+
89
+ # Enable provisioning with chef solo, specifying a cookbooks path, roles
90
+ # path, and data_bags path (all relative to this Vagrantfile), and adding
91
+ # some recipes and/or roles.
92
+ #
93
+ # config.vm.provision "chef_solo" do |chef|
94
+ # chef.cookbooks_path = "../my-recipes/cookbooks"
95
+ # chef.roles_path = "../my-recipes/roles"
96
+ # chef.data_bags_path = "../my-recipes/data_bags"
97
+ # chef.add_recipe "mysql"
98
+ # chef.add_role "web"
99
+ #
100
+ # # You may also specify custom JSON attributes:
101
+ # chef.json = { mysql_password: "foo" }
102
+ # end
103
+
104
+ # Enable provisioning with chef server, specifying the chef server URL,
105
+ # and the path to the validation key (relative to this Vagrantfile).
106
+ #
107
+ # The Opscode Platform uses HTTPS. Substitute your organization for
108
+ # ORGNAME in the URL and validation key.
109
+ #
110
+ # If you have your own Chef Server, use the appropriate URL, which may be
111
+ # HTTP instead of HTTPS depending on your configuration. Also change the
112
+ # validation key to validation.pem.
113
+ #
114
+ # config.vm.provision "chef_client" do |chef|
115
+ # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
116
+ # chef.validation_key_path = "ORGNAME-validator.pem"
117
+ # end
118
+ #
119
+ # If you're using the Opscode platform, your validator client is
120
+ # ORGNAME-validator, replacing ORGNAME with your organization name.
121
+ #
122
+ # If you have your own Chef Server, the default validation client name is
123
+ # chef-validator, unless you changed the configuration.
124
+ #
125
+ # chef.validation_client_name = "ORGNAME-validator"
126
+ end