thtp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f17bdb7ed51a45d7e5dc775bd2cedd63b61aab9b8f4bcd8d23a8df04b4d4efc5
4
+ data.tar.gz: a6ae6b43a98aaa113eba27f9fbcfd4c6d90f94a729ef55b310dc40bb5e599094
5
+ SHA512:
6
+ metadata.gz: fccd24ae3db0e6628905d07d4a1b1f707ab571e0cc2b31ba156036ed8e0f411c9655b85d338ff3c6dc39cf1781032ffe2b718f0732b3c8b000bec8debc20476b
7
+ data.tar.gz: ab7ba5e703ca28e490dbdfa1b62dd9fa9dbe453ee39a6794711107ef5bb0b768f397a16f577f6bbde3288e826f4af8f0a7f3d857df4531666370e771b309a250
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ /Gemfile.lock
11
+
12
+ .rspec_status
13
+
14
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,190 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+ DisplayCopNames: true
4
+ UseCache: true
5
+ CacheRootDirectory: ./tmp/cache
6
+
7
+ # This is unnecessary since there are already other ways of measuring method
8
+ # size/complexity.
9
+ AbcSize:
10
+ Enabled: false
11
+
12
+ AccessModifierIndentation:
13
+ Enabled: true
14
+ EnforcedStyle: indent
15
+
16
+ # This lint prohibits method names starting with `get_`, which is often good
17
+ # advice but wrong too often to be worthwhile.
18
+ AccessorMethodName:
19
+ Enabled: false
20
+
21
+ # Allow developers to pass regexes as arguments without enclosing parens, e.g.
22
+ # response.body.should match /some regex/
23
+ AmbiguousRegexpLiteral:
24
+ Enabled: false
25
+
26
+ # Allow the case equality ("threequals") operator, which is often useful,
27
+ # especially for working with RSpec.
28
+ CaseEquality:
29
+ Enabled: false
30
+
31
+ ClassLength:
32
+ Severity: warning
33
+
34
+ # Don't require use of `collect` over `map`
35
+ CollectionMethods:
36
+ Enabled: false
37
+
38
+ CommentAnnotation:
39
+ Enabled: false
40
+
41
+ CyclomaticComplexity:
42
+ Severity: warning
43
+
44
+ Debugger:
45
+ Severity: error # binding.pry blocks!
46
+
47
+ DoubleNegation:
48
+ Enabled: false
49
+
50
+ FirstParameterIndentation:
51
+ Enabled: false
52
+
53
+ # Don't enforce using if/unless modifiers when you have a single-line body.
54
+ IfUnlessModifier:
55
+ Enabled: false
56
+
57
+ # We want to allow multi-line lambdas using the `->` syntax which Rubocop
58
+ # doesn't allow. We're also not too worried about people using `lambda` for
59
+ # single-line lambdas either.
60
+ Lambda:
61
+ Enabled: false
62
+
63
+ LineLength:
64
+ Max: 100
65
+ IgnoredPatterns:
66
+ - '\A#' # Allow longer comments
67
+
68
+ # There are a number of places where it makes sense to chain do...end blocks
69
+ MethodCalledOnDoEndBlock:
70
+ Enabled: false
71
+
72
+ MethodLength:
73
+ Severity: warning
74
+ Max: 30
75
+
76
+ Metrics/ModuleLength:
77
+ Severity: warning
78
+
79
+ Metrics/BlockLength:
80
+ Severity: warning
81
+ Exclude:
82
+ - spec/**/*
83
+
84
+ # Because we sometimes use RSpec's implicit subject syntax, we need to be able
85
+ # to format blocks more flexibly than in the main codebase.
86
+ MultilineBlockLayout:
87
+ Exclude:
88
+ - spec/**/*
89
+
90
+ ParameterLists:
91
+ CountKeywordArgs: false
92
+
93
+ PerceivedComplexity:
94
+ Severity: warning
95
+
96
+ # Prefer curly braces except for %i/%w/%W, since those return arrays.
97
+ PercentLiteralDelimiters:
98
+ PreferredDelimiters:
99
+ '%': '{}'
100
+ '%i': '[]'
101
+ '%q': '{}'
102
+ '%Q': '{}'
103
+ '%r': '{}'
104
+ '%s': '()'
105
+ '%w': '[]'
106
+ '%W': '[]'
107
+ '%x': '{}'
108
+
109
+ # Forcing the name of a predicate to `doctor?` makes it difficult to tell if it
110
+ # is a "has-a" or a "is-a" predicate, so disable it.
111
+ PredicateName:
112
+ Enabled: false
113
+
114
+ ShadowingOuterLocalVariable:
115
+ Severity: error
116
+
117
+ # Forcing the naming of arguments to `reduce` to be `|a, e|` isn't very useful.
118
+ SingleLineBlockParams:
119
+ Enabled: false
120
+
121
+ Layout/SpaceBeforeFirstArg:
122
+ AllowForAlignment: true
123
+
124
+ # There's no reason to enforce only using ASCII in comments.
125
+ Style/AsciiComments:
126
+ Enabled: false
127
+
128
+ Style/ModuleFunction:
129
+ Enabled: false
130
+
131
+ Layout/MultilineMethodCallIndentation:
132
+ EnforcedStyle: indented
133
+
134
+ Layout/MultilineMethodCallBraceLayout:
135
+ Enabled: false
136
+
137
+ Style/NumericLiterals:
138
+ Exclude:
139
+ - spec/**/*
140
+
141
+ # Middlewares have to do this all the time
142
+ Style/RescueStandardError:
143
+ Enabled: false
144
+
145
+ # It doesn't seem like .zero? and .nonzero? are more readable than == 0
146
+ Style/NumericPredicate:
147
+ Enabled: false
148
+
149
+ Style/TrailingCommaInArrayLiteral:
150
+ EnforcedStyleForMultiline: comma
151
+
152
+ Style/TrailingCommaInHashLiteral:
153
+ EnforcedStyleForMultiline: comma
154
+
155
+ Style/TrailingCommaInArguments:
156
+ EnforcedStyleForMultiline: comma
157
+
158
+ UnreachableCode:
159
+ Severity: error
160
+
161
+ UnusedMethodArgument:
162
+ AllowUnusedKeywordArguments: true
163
+
164
+ # Wastes CPU time, especially if the right-hand expression is expensive
165
+ UselessAssignment:
166
+ Severity: error
167
+
168
+ WhileUntilModifier:
169
+ Enabled: false
170
+
171
+ # Two strings in an array is not indicative that the array is likely to have
172
+ # elements added to it in the future, so up the minimum number of elements we
173
+ # warn on to 3.
174
+ WordArray:
175
+ MinSize: 3
176
+
177
+ Layout/ClosingParenthesisIndentation:
178
+ Enabled: false
179
+
180
+ Style/SpecialGlobalVars:
181
+ Enabled: false
182
+
183
+ # Checks for frozen string comment, designed to help upgrade to Ruby 3.0 (where
184
+ # frozen string literals will be default); disabled because we prefer calling `.freeze`
185
+ # on our string literals rather than using a magic comment.
186
+ Style/FrozenStringLiteralComment:
187
+ Enabled: false
188
+
189
+ Layout/DotPosition:
190
+ EnforcedStyle: trailing
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Anuj Das
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # THTP
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/thtp`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'thtp'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install thtp
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/thtp.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'thtp'
5
+
6
+ require 'irb'
7
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/thtp.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'thtp/version'
2
+ require 'thtp/client'
3
+ require 'thtp/server'
4
+
5
+ # THTP implements an alternative paradigm for Thrift-RPC services, using
6
+ # HTTP/1.1 persistent connections to gain many of the same benefits of the
7
+ # low-level Thrift-RPC socket protocol while benefitting from all the tooling
8
+ # available for HTTP. Both server and client implementations are provided.
9
+ module THTP
10
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'connection_pool'
4
+ require 'patron'
5
+ require 'thrift'
6
+
7
+ require 'thtp/encoding'
8
+ require 'thtp/errors'
9
+ require 'thtp/middleware_stack'
10
+ require 'thtp/status'
11
+ require 'thtp/utils'
12
+
13
+ require 'thtp/client/instrumentation'
14
+ require 'thtp/client/middleware'
15
+
16
+ module THTP
17
+ # A thrift-over-HTTP client library implementing persistent connections and
18
+ # extensibility via middlewares
19
+ class Client
20
+ include Utils
21
+
22
+ # RPC-over-HTTP protocol implementation and executor
23
+ class Dispatcher
24
+ include Utils
25
+
26
+ SUCCESS_FIELD = 'success' # the Thrift result field that's set if everything went fine
27
+
28
+ # @param service [Class] The Thrift service whose schema to use for de/serialisation
29
+ # @parma connection [ConnectionPool<Patron::Session>] The configured HTTP client pool
30
+ # @param protocol [Thrift::BaseProtocol] The default protocol with which to serialise
31
+ def initialize(service, connection, protocol)
32
+ @service = service
33
+ @connection = connection
34
+ @protocol = protocol
35
+ # define RPC proxy methods on this instance
36
+ extract_rpcs(service).each do |rpc|
37
+ define_singleton_method(rpc) { |*rpc_args| post_rpc(rpc, *rpc_args) }
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def post_rpc(rpc, *args)
44
+ # send request over persistent HTTP connection
45
+ response = @connection.with { |c| c.post(rpc, write_call(rpc, args)) }
46
+ # interpret HTTP status code to determine message type and deserialise appropriately
47
+ protocol = Encoding.protocol(response.headers['Content-Type']) || @protocol
48
+ return read_reply(rpc, response.body, protocol) if response.status == Status::REPLY
49
+ return read_exception(response.body, protocol) if response.status == Status::EXCEPTION
50
+ # if the HTTP status code was unrecognised, report back
51
+ raise UnknownMessageType, rpc, response.status, response.body
52
+ rescue Patron::TimeoutError, Timeout::Error
53
+ raise RpcTimeoutError, rpc
54
+ rescue Patron::Error
55
+ raise ServerUnreachableError
56
+ end
57
+
58
+ def write_call(rpc, args)
59
+ # args are named methods, but RPC signatures use positional arguments;
60
+ # convert between the two using struct_fields, which is an ordered hash.
61
+ args_struct = args_class(@service, rpc).new
62
+ args_struct.struct_fields.values.zip(args).each do |field, val|
63
+ args_struct.public_send("#{field[:name]}=", val)
64
+ end
65
+ # serialise and return bytestring
66
+ serialize_buffer(args_struct, @protocol)
67
+ rescue Thrift::TypeError => e
68
+ raise ClientValidationError, e.message
69
+ end
70
+
71
+ def read_reply(rpc, reply, protocol)
72
+ # deserialise reply into result struct
73
+ result_struct = result_class(@service, rpc).new
74
+ deserialize_buffer(reply, result_struct, protocol)
75
+ # results have at most one field set; find it and return/raise it
76
+ result_struct.struct_fields.each_value do |field|
77
+ reply = result_struct.public_send(field[:name])
78
+ next if reply.nil? # this isn't the set field, keep looking
79
+ return reply if field[:name] == SUCCESS_FIELD # 'success' is special and means no worries
80
+ raise reply # any other set field must be an exception
81
+ end
82
+ # if no field is set and there's no `success` field, the RPC returned `void``
83
+ return nil unless result_struct.respond_to?(:success)
84
+ # otherwise, we don't recognise the response (our schema is out of date, or it's invalid)
85
+ raise BadResponseError, rpc
86
+ end
87
+
88
+ def read_exception(exception, protocol)
89
+ raise deserialize_buffer(exception, Thrift::ApplicationException.new, protocol)
90
+ end
91
+ end
92
+
93
+ ###
94
+
95
+ # @param service [Class] The Thrift service whose schema to use for de/serialisation
96
+ def initialize(service, protocol: Thrift::CompactProtocol,
97
+ host: '0.0.0.0', port: nil, ssl: false,
98
+ open_timeout: 1, rpc_timeout: 15,
99
+ pool_size: 5, pool_timeout: 5)
100
+ uri_class = ssl ? URI::HTTPS : URI::HTTP
101
+ # set up HTTP connections in a thread-safe pool
102
+ connection = ConnectionPool.new(size: pool_size, timeout: pool_timeout) do
103
+ Patron::Session.new(
104
+ base_url: uri_class.build(host: host, port: port, path: "/#{canonical_name(service)}/"),
105
+ connect_timeout: open_timeout,
106
+ timeout: rpc_timeout,
107
+ headers: {
108
+ 'Content-Type' => Encoding.content_type(protocol),
109
+ 'User-Agent' => self.class.name,
110
+ },
111
+ )
112
+ end
113
+ # allow middleware insertion for purposes such as instrumentation or validation
114
+ @stack = MiddlewareStack.new(service, Dispatcher.new(service, connection, protocol))
115
+ extract_rpcs(service).each { |rpc| define_singleton_method(rpc, &@stack.method(rpc)) }
116
+ end
117
+
118
+ # delegate to RPC dispatcher stack
119
+ def use(middleware_class, *middleware_args)
120
+ @stack.use(middleware_class, *middleware_args)
121
+ end
122
+ end
123
+ end