restify 1.4.4 → 1.5.0

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
  SHA256:
3
- metadata.gz: 05b4120c9755cdd5a88f172e3d5964eff3804add46785bd3cbec8b842f2af459
4
- data.tar.gz: 5cb09e31e263560b373b156cfaa601925b9bde328ff892d4533c2738b10dff67
3
+ metadata.gz: 8910f0a86f7bab64dbcfe43794c8fddbe8d37fb289845105718f61de3228f245
4
+ data.tar.gz: 843c1fd568747ed129589f08a8d7e3e040315f49f49620e46cd40265e1df2411
5
5
  SHA512:
6
- metadata.gz: a5854443d09b5d6ce73f3a68da7c76e2ed7453288b4e9ef940cfb2e437b8666f0c4f03b6602405193728751b5ee2cf46a12937196d412e3d911d7b497e947daf
7
- data.tar.gz: 535598e592a526d20cb8c5d93fe4926deea1d1f92f60bf5fd003115a56841462ad6ce475d96927a09b940524c622e80730ff06c858940c2d08e4ed8396fa8359
6
+ metadata.gz: 1697fecae910ceaf8c93f40416dcef77a1eba6a5bb0aa9fb67fffa520a7191fb273ac5a5bb9202ee00a7eec6ad559ccebd56f07b1a7c572077f4d44c9f7d3500
7
+ data.tar.gz: 4ad078a7595c6fdf7fc867eb367f658c7e1cec5297845467547668177834ad5722447520de2d3bcf1c0c1ff0d7924c903b7fe55cbe443bfa33605b5de2a8da0a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.0
4
+
5
+ * Tune typhoeus adapter to be more race-condition resilent
6
+ * Add MessagePack processor enabled by default
7
+
3
8
  ## 1.4.4
4
9
 
5
10
  * Fix race condition in typhoeus adapter
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'typhoeus'
4
4
 
5
+ ::Ethon.logger = ::Logging.logger[Ethon]
6
+
5
7
  module Restify
6
8
  module Adapter
7
9
  class Typhoeus < Base
@@ -26,30 +28,26 @@ module Restify
26
28
  end
27
29
 
28
30
  def call_native(request, writer)
29
- @mutex.synchronize do
30
- logger.debug { "[#{request.object_id}] Queue request #{request}" }
31
- @hydra.queue convert(request, writer)
32
- @hydra.dequeue_many
33
-
34
- if sync?
35
- @hydra.run
36
- else
37
- thread.run
38
- @signal.signal
31
+ req = convert(request, writer)
32
+
33
+ if sync?
34
+ req.run
35
+ else
36
+ @mutex.synchronize do
37
+ logger.debug { "[#{self.object_id}/#{Thread.current.object_id}] [#{request.object_id}] request:add method=#{request.method.upcase} url=#{request.uri}" }
38
+ @hydra.queue(req)
39
+ @hydra.dequeue_many
40
+
41
+ thread.run unless thread.status
39
42
  end
40
- end
41
- end
42
43
 
43
- def queued?
44
- @mutex.synchronize { ns_queued? }
44
+ logger.debug { "[#{self.object_id}/#{Thread.current.object_id}] [#{request.object_id}] request:signal" }
45
+ @signal.signal
46
+ end
45
47
  end
46
48
 
47
49
  private
48
50
 
49
- def ns_queued?
50
- @hydra.queued_requests.any? || @hydra.multi.easy_handles.count > 0
51
- end
52
-
53
51
  def convert(request, writer)
54
52
  ::Typhoeus::Request.new(
55
53
  request.uri,
@@ -61,7 +59,7 @@ module Restify
61
59
  connecttimeout: request.timeout
62
60
  ).tap do |req|
63
61
  req.on_complete do |response|
64
- logger.debug { "[#{request.object_id}] Completed: #{response.code}" }
62
+ logger.debug { "[#{self.object_id}/#{Thread.current.object_id}] [#{request.object_id}] request:complete status=#{response.code}" }
65
63
 
66
64
  if response.timed_out?
67
65
  writer.reject Restify::Timeout.new request
@@ -94,6 +92,7 @@ module Restify
94
92
  def thread
95
93
  if @thread.nil? || !@thread.status
96
94
  # Recreate thread if nil or dead
95
+ logger.debug { "[#{self.object_id}/#{Thread.current.object_id}] hydra:spawn" }
97
96
  @thread = Thread.new { _loop }
98
97
  end
99
98
 
@@ -101,20 +100,25 @@ module Restify
101
100
  end
102
101
 
103
102
  def _loop
103
+ Thread.current.name = 'Restify/Typhoeus Background'
104
104
  loop { _run }
105
105
  end
106
106
 
107
+ def _ongoing?
108
+ @hydra.queued_requests.any? || @hydra.multi.easy_handles.any?
109
+ end
110
+
107
111
  def _run
108
- if queued?
109
- logger.debug { 'Run hydra' }
110
- @hydra.run
111
- logger.debug { 'Hydra finished' }
112
- else
113
- @mutex.synchronize do
114
- return if ns_queued?
115
- logger.debug { 'Pause hydra thread' }
116
- @signal.wait(@mutex)
117
- end
112
+ logger.debug { "[#{self.object_id}/#{Process.pid}] hydra:run" }
113
+ @hydra.run while _ongoing?
114
+ logger.debug { "[#{self.object_id}/#{Thread.current.object_id}] hydra:completed" }
115
+
116
+ @mutex.synchronize do
117
+ return if _ongoing?
118
+
119
+ logger.debug { "[#{self.object_id}/#{Thread.current.object_id}] hydra:pause" }
120
+ @signal.wait(@mutex, 60)
121
+ logger.debug { "[#{self.object_id}/#{Thread.current.object_id}] hydra:resumed" }
118
122
  end
119
123
  rescue StandardError => e
120
124
  logger.error(e)
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Restify
6
+ module Processors
7
+ class Base
8
+ #
9
+ # Parses generic data structures into resources
10
+ #
11
+ module Parsing
12
+ def self.included(base)
13
+ base.extend ClassMethods
14
+ base.indifferent_access = true
15
+ end
16
+
17
+ def load
18
+ parse deserialized_body, root: true
19
+ end
20
+
21
+ def parse(object, root: false)
22
+ case object
23
+ when Hash
24
+ data = object.each_with_object({}, &method(:parse_data))
25
+ relations = object.each_with_object({}, &method(:parse_rels))
26
+
27
+ if self.class.indifferent_access?
28
+ data = with_indifferent_access(data)
29
+ end
30
+
31
+ Resource.new context,
32
+ data: data,
33
+ response: root ? response : nil,
34
+ relations: relations
35
+
36
+ when Array
37
+ object.map(&method(:parse))
38
+ else
39
+ object
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def parse_data(pair, data)
46
+ data[pair[0].to_s] = parse pair[1]
47
+ end
48
+
49
+ def parse_rels(pair, relations)
50
+ name = case pair[0].to_s.downcase
51
+ when /\A(\w+)_url\z/
52
+ Regexp.last_match[1]
53
+ when 'url'
54
+ 'self'
55
+ else
56
+ return
57
+ end
58
+
59
+ if relations.key?(name) || pair[1].nil? || pair[1].to_s =~ /\A\w*\z/
60
+ return
61
+ end
62
+
63
+ relations[name] = pair[1].to_s
64
+ end
65
+
66
+ def with_indifferent_access(data)
67
+ Hashie::Mash.new data
68
+ end
69
+
70
+ module ClassMethods
71
+ def indifferent_access?
72
+ @indifferent_access
73
+ end
74
+
75
+ attr_writer :indifferent_access
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -3,7 +3,6 @@
3
3
  require 'json'
4
4
 
5
5
  module Restify
6
- #
7
6
  module Processors
8
7
  #
9
8
  # Decode plain JSON responses.
@@ -11,82 +10,17 @@ module Restify
11
10
  # JSON fields matching *_url will be parsed as relations.
12
11
  #
13
12
  class Json < Base
14
- def load
15
- parse ::JSON.parse(body), root: true
16
- end
17
-
18
- private
19
-
20
- # rubocop:disable Metrics/MethodLength
21
- def parse(object, root: false)
22
- case object
23
- when Hash
24
- data = object.each_with_object({}, &method(:parse_data))
25
- relations = object.each_with_object({}, &method(:parse_rels))
26
-
27
- if self.class.indifferent_access?
28
- data = with_indifferent_access(data)
29
- end
30
-
31
- if root
32
- Resource.new context,
33
- data: data,
34
- response: response,
35
- relations: relations
36
- else
37
- Resource.new context,
38
- data: data,
39
- relations: relations
40
- end
41
- when Array
42
- object.map(&method(:parse))
43
- else
44
- object
45
- end
46
- end
47
-
48
- def parse_data(pair, data)
49
- data[pair[0].to_s] = parse pair[1]
50
- end
51
-
52
- def parse_rels(pair, relations)
53
- name = case pair[0].to_s.downcase
54
- when /\A(\w+)_url\z/
55
- Regexp.last_match[1]
56
- when 'url'
57
- 'self'
58
- else
59
- return
60
- end
13
+ include Parsing
61
14
 
62
- if relations.key?(name) || pair[1].nil? || pair[1].to_s =~ /\A\w*\z/
63
- return
64
- end
65
-
66
- relations[name] = pair[1].to_s
67
- end
68
-
69
- def json
70
- @json ||= JSON.parse(response.body)
71
- end
72
-
73
- def with_indifferent_access(data)
74
- Hashie::Mash.new data
15
+ def deserialized_body
16
+ ::JSON.parse(body)
75
17
  end
76
18
 
77
19
  class << self
78
20
  def accept?(response)
79
21
  response.content_type =~ %r{\Aapplication/json($|;)}
80
22
  end
81
-
82
- def indifferent_access?
83
- @indifferent_access
84
- end
85
-
86
- attr_writer :indifferent_access
87
23
  end
88
-
89
- self.indifferent_access = true
90
24
  end
91
25
  end
92
26
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msgpack'
4
+
5
+ module Restify
6
+ module Processors
7
+ #
8
+ # Decode messagepack encoded responses.
9
+ #
10
+ # Fields matching *_url will be parsed as relations.
11
+ #
12
+ class Msgpack < Base
13
+ include Parsing
14
+
15
+ def deserialized_body
16
+ ::MessagePack.unpack(body)
17
+ end
18
+
19
+ class << self
20
+ def accept?(response)
21
+ response.content_type =~ %r{\Aapplication/(x-)?msgpack($|;)}
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -3,8 +3,8 @@
3
3
  module Restify
4
4
  module VERSION
5
5
  MAJOR = 1
6
- MINOR = 4
7
- PATCH = 4
6
+ MINOR = 5
7
+ PATCH = 0
8
8
  STAGE = nil
9
9
  STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.').freeze
10
10
 
data/lib/restify.rb CHANGED
@@ -9,7 +9,6 @@ require 'concurrent'
9
9
  require 'addressable/uri'
10
10
  require 'addressable/template'
11
11
 
12
- #
13
12
  module Restify
14
13
  require 'restify/error'
15
14
  require 'restify/logging'
@@ -34,10 +33,13 @@ module Restify
34
33
 
35
34
  module Processors
36
35
  require 'restify/processors/base'
36
+ require 'restify/processors/base/parsing'
37
+
37
38
  require 'restify/processors/json'
39
+ require 'restify/processors/msgpack'
38
40
  end
39
41
 
40
- PROCESSORS = [Processors::Json].freeze
42
+ PROCESSORS = [Processors::Json, Processors::Msgpack].freeze
41
43
 
42
44
  extend Global
43
45
  end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Processors::Msgpack do
6
+ let(:context) { Restify::Context.new('http://test.host/') }
7
+ let(:response) { double 'response' }
8
+
9
+ before do
10
+ allow(response).to receive(:links).and_return []
11
+ allow(response).to receive(:follow_location).and_return nil
12
+ end
13
+
14
+ describe 'class' do
15
+ describe '#accept?' do
16
+ subject { described_class.accept? response }
17
+
18
+ it 'accepts msgpack mime type (I)' do
19
+ expect(response).to receive(:content_type).and_return('application/msgpack')
20
+ expect(subject).to be_truthy
21
+ end
22
+
23
+ it 'accepts msgpack mime type (II)' do
24
+ expect(response).to receive(:content_type).and_return('application/msgpack; abc')
25
+ expect(subject).to be_truthy
26
+ end
27
+
28
+ it 'accepts x-msgpack mime type (I)' do
29
+ expect(response).to receive(:content_type).and_return('application/x-msgpack')
30
+ expect(subject).to be_truthy
31
+ end
32
+
33
+ it 'accepts x-msgpack mime type (II)' do
34
+ expect(response).to receive(:content_type).and_return('application/x-msgpack; abc')
35
+ expect(subject).to be_truthy
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#resource' do
41
+ subject { described_class.new(context, response).resource }
42
+ before { allow(response).to receive(:body).and_return(body) }
43
+
44
+ describe 'parsing' do
45
+ context 'single object' do
46
+ let(:body) do
47
+ ::MessagePack.dump('msg' => 'value')
48
+ end
49
+
50
+ it { is_expected.to be_a Restify::Resource }
51
+ it { expect(subject.response).to be response }
52
+ it { is_expected.to eq 'msg' => 'value' }
53
+ end
54
+
55
+ context 'object with relation fields' do
56
+ let(:body) do
57
+ ::MessagePack.dump(
58
+ 'msg' => 'value',
59
+ 'search_url' => 'https://google.com{?q}'
60
+ )
61
+ end
62
+
63
+ it do
64
+ is_expected.to eq \
65
+ 'msg' => 'value', 'search_url' => 'https://google.com{?q}'
66
+ end
67
+
68
+ it { is_expected.to have_relation :search }
69
+ it { expect(subject.relation(:search)).to eq 'https://google.com{?q}' }
70
+ end
71
+
72
+ context 'object with implicit self relation' do
73
+ let(:body) do
74
+ ::MessagePack.dump(
75
+ 'url' => '/self'
76
+ )
77
+ end
78
+
79
+ it { expect(subject.relation(:self)).to eq '/self' }
80
+ end
81
+
82
+ context 'single array' do
83
+ let(:body) do
84
+ ::MessagePack.dump([1, 2, nil, 'STR'])
85
+ end
86
+
87
+ it { is_expected.to be_a Restify::Resource }
88
+ it { expect(subject.response).to be response }
89
+ it { is_expected.to eq [1, 2, nil, 'STR'] }
90
+ end
91
+
92
+ context 'array with objects' do
93
+ let(:body) do
94
+ ::MessagePack.dump([{'a' => 0}, {'b' => 1}])
95
+ end
96
+
97
+ it { is_expected.to eq [{'a' => 0}, {'b' => 1}] }
98
+ end
99
+
100
+ context 'array with resources' do
101
+ let(:body) do
102
+ ::MessagePack.dump([
103
+ {'name' => 'John', 'self_url' => '/users/john'},
104
+ {'name' => 'Jane', 'self_url' => '/users/jane'}
105
+ ])
106
+ end
107
+
108
+ it 'parses objects as resources' do
109
+ expect(subject).to all(be_a(Restify::Resource))
110
+ end
111
+
112
+ it 'parses relations of resources' do
113
+ expect(subject.map {|r| r.relation :self }).to eq \
114
+ ['/users/john', '/users/jane']
115
+ end
116
+ end
117
+
118
+ context 'nested objects' do
119
+ let(:body) do
120
+ ::MessagePack.dump(
121
+ 'john' => {'name' => 'John'},
122
+ 'jane' => {'name' => 'Jane'}
123
+ )
124
+ end
125
+
126
+ it { is_expected.to be_a Restify::Resource }
127
+ it { expect(subject.response).to be response }
128
+
129
+ it 'parses objects as resources' do
130
+ expect(subject['john']).to be_a Restify::Resource
131
+ expect(subject['jane']).to be_a Restify::Resource
132
+
133
+ expect(subject['john']['name']).to eq 'John'
134
+ expect(subject['jane']['name']).to eq 'Jane'
135
+ end
136
+ end
137
+
138
+ context 'single value' do
139
+ let(:body) do
140
+ ::MessagePack.dump('BLUB')
141
+ end
142
+
143
+ it { is_expected.to be_a Restify::Resource }
144
+ it { expect(subject.response).to be response }
145
+ it { is_expected.to eq 'BLUB' }
146
+ end
147
+
148
+ context 'with indifferent access' do
149
+ let(:body) do
150
+ ::MessagePack.dump('name' => 'John', 'age' => 24)
151
+ end
152
+
153
+ it '#key?' do
154
+ expect(subject).to have_key 'name'
155
+ expect(subject).to have_key 'age'
156
+
157
+ expect(subject).to have_key :name
158
+ expect(subject).to have_key :age
159
+ end
160
+
161
+ it '#[]' do
162
+ expect(subject['name']).to eq 'John'
163
+ expect(subject['age']).to eq 24
164
+
165
+ expect(subject[:name]).to eq 'John'
166
+ expect(subject[:age]).to eq 24
167
+ end
168
+ end
169
+
170
+ context 'with method getter access' do
171
+ let(:body) do
172
+ ::MessagePack.dump('name' => 'John', 'age' => 24)
173
+ end
174
+
175
+ it '#<method getter>' do
176
+ expect(subject).to respond_to :name
177
+ expect(subject).to respond_to :age
178
+
179
+ expect(subject.name).to eq 'John'
180
+ expect(subject.age).to eq 24
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.4
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Graichen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-13 00:00:00.000000000 Z
11
+ date: 2018-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '2.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: msgpack
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.2'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.2'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: bundler
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -145,7 +159,9 @@ files:
145
159
  - lib/restify/link.rb
146
160
  - lib/restify/logging.rb
147
161
  - lib/restify/processors/base.rb
162
+ - lib/restify/processors/base/parsing.rb
148
163
  - lib/restify/processors/json.rb
164
+ - lib/restify/processors/msgpack.rb
149
165
  - lib/restify/promise.rb
150
166
  - lib/restify/registry.rb
151
167
  - lib/restify/relation.rb
@@ -159,6 +175,7 @@ files:
159
175
  - spec/restify/link_spec.rb
160
176
  - spec/restify/processors/base_spec.rb
161
177
  - spec/restify/processors/json_spec.rb
178
+ - spec/restify/processors/msgpack_spec.rb
162
179
  - spec/restify/promise_spec.rb
163
180
  - spec/restify/registry_spec.rb
164
181
  - spec/restify/relation_spec.rb
@@ -196,6 +213,7 @@ test_files:
196
213
  - spec/restify/link_spec.rb
197
214
  - spec/restify/processors/base_spec.rb
198
215
  - spec/restify/processors/json_spec.rb
216
+ - spec/restify/processors/msgpack_spec.rb
199
217
  - spec/restify/promise_spec.rb
200
218
  - spec/restify/registry_spec.rb
201
219
  - spec/restify/relation_spec.rb