appsignal 3.6.1-java → 3.6.2-java

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73f661b3ecb2fd8f52080f6a08b4f125c2b217f6e50c739cf474cad7c7f4d4fa
4
- data.tar.gz: 7729d6172b3f0df044241548f347e2d22bdd0b295454a9bae93cfbc6f72119db
3
+ metadata.gz: 3a301e6247ef3ffd84430308f44d847483807a96c04356346fb0a281ad9bea0f
4
+ data.tar.gz: ae2bd9370beec37c374885e5bf10b5316394243ed73038f5d377553734789597
5
5
  SHA512:
6
- metadata.gz: 58f99d19036363569ba38fd5c07af32b24b6814d538c8ad639b7150ea463d42277e4508618094e6ed9055896be1b29ec2461d50db58fdadbbdcbf5c7b75eff69
7
- data.tar.gz: 252ed529380e877ba34d3a5b7d921f042835e5d9baab95010fda67d195aa0ee647895f8d0c12a18a086bfc7594179155c83c3635eb3afd66ef265a3fa484a36b
6
+ metadata.gz: a1ffcccbd922782d969fabd46828571c9137905674943f0aa790df83fcae7c71cfcc5ac26ab5a6d5227f1e461061ff00da8ccf38ade23bb86fe7c41137774251
7
+ data.tar.gz: 9e732c7b94dfa76013a8def22d31202feb6c7e4ecffb74a74408e2787ed8ae7b98fd8477b9a2af7242edc35d912d65f2c60a9228ef2ce019574082919ae4d622
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 3.6.2
4
+
5
+ _Published on 2024-03-08._
6
+
7
+ ### Fixed
8
+
9
+ - [c3921865](https://github.com/appsignal/appsignal-ruby/commit/c392186573a72fd9afe22299fabcd14dcfe96139) patch - Revert Rack middleware changes (see [changelog](https://github.com/appsignal/appsignal-ruby/blob/main/CHANGELOG.md#360)) to fix issues relating to Unicorn broken pipe errors and multiple requests merging into a single sample.
10
+
3
11
  ## 3.6.1
4
12
 
5
13
  _Published on 2024-03-05._
@@ -16,9 +16,7 @@ module Appsignal
16
16
  if Appsignal.active?
17
17
  call_with_appsignal_monitoring(env)
18
18
  else
19
- nil_transaction = Appsignal::Transaction::NilTransaction.new
20
- status, headers, obody = @app.call(env)
21
- [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
19
+ @app.call(env)
22
20
  end
23
21
  end
24
22
 
@@ -29,30 +27,19 @@ module Appsignal
29
27
  Appsignal::Transaction::HTTP_REQUEST,
30
28
  request
31
29
  )
32
- # We need to complete the transaction if there is an exception inside the `call`
33
- # of the app. If there isn't one and the app returns us a Rack response triplet, we let
34
- # the BodyWrapper complete the transaction when #close gets called on it
35
- # (guaranteed by the webserver)
36
- complete_transaction_without_body = false
37
30
  begin
38
31
  Appsignal.instrument("process_action.generic") do
39
- status, headers, obody = @app.call(env)
40
- [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
32
+ @app.call(env)
41
33
  end
42
34
  rescue Exception => error # rubocop:disable Lint/RescueException
43
35
  transaction.set_error(error)
44
- complete_transaction_without_body = true
45
36
  raise error
46
37
  ensure
47
- default_action = env["appsignal.route"] || env["appsignal.action"] || "unknown"
48
- transaction.set_action_if_nil(default_action)
38
+ transaction.set_action_if_nil(env["appsignal.route"] || "unknown")
49
39
  transaction.set_metadata("path", request.path)
50
40
  transaction.set_metadata("method", request.request_method)
51
41
  transaction.set_http_or_background_queue_start
52
-
53
- # Transaction gets completed when the body gets read out, except in cases when
54
- # the app failed before returning us the Rack response triplet.
55
- Appsignal::Transaction.complete_current! if complete_transaction_without_body
42
+ Appsignal::Transaction.complete_current!
56
43
  end
57
44
  end
58
45
  end
@@ -16,9 +16,7 @@ module Appsignal
16
16
  if Appsignal.active?
17
17
  call_with_appsignal_monitoring(env)
18
18
  else
19
- nil_transaction = Appsignal::Transaction::NilTransaction.new
20
- status, headers, obody = @app.call(env)
21
- [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
19
+ @app.call(env)
22
20
  end
23
21
  end
24
22
 
@@ -30,17 +28,10 @@ module Appsignal
30
28
  request,
31
29
  :params_method => :filtered_parameters
32
30
  )
33
- # We need to complete the transaction if there is an exception exception inside the `call`
34
- # of the app. If there isn't one and the app returns us a Rack response triplet, we let
35
- # the BodyWrapper complete the transaction when #close gets called on it
36
- # (guaranteed by the webserver)
37
- complete_transaction_without_body = false
38
31
  begin
39
- status, headers, obody = @app.call(env)
40
- [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
32
+ @app.call(env)
41
33
  rescue Exception => error # rubocop:disable Lint/RescueException
42
34
  transaction.set_error(error)
43
- complete_transaction_without_body = true
44
35
  raise error
45
36
  ensure
46
37
  controller = env["action_controller.instance"]
@@ -54,10 +45,7 @@ module Appsignal
54
45
  rescue => error
55
46
  Appsignal.internal_logger.error("Unable to report HTTP request method: '#{error}'")
56
47
  end
57
-
58
- # Transaction gets completed when the body gets read out, except in cases when
59
- # the app failed before returning us the Rack response triplet.
60
- Appsignal::Transaction.complete_current! if complete_transaction_without_body
48
+ Appsignal::Transaction.complete_current!
61
49
  end
62
50
  end
63
51
 
@@ -42,9 +42,7 @@ module Appsignal
42
42
  if Appsignal.active?
43
43
  call_with_appsignal_monitoring(env)
44
44
  else
45
- nil_transaction = Appsignal::Transaction::NilTransaction.new
46
- status, headers, obody = @app.call(env)
47
- [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
45
+ @app.call(env)
48
46
  end
49
47
  end
50
48
 
@@ -58,19 +56,12 @@ module Appsignal
58
56
  request,
59
57
  options
60
58
  )
61
- # We need to complete the transaction if there is an exception exception inside the `call`
62
- # of the app. If there isn't one and the app returns us a Rack response triplet, we let
63
- # the BodyWrapper complete the transaction when #close gets called on it
64
- # (guaranteed by the webserver)
65
- complete_transaction_without_body = false
66
59
  begin
67
60
  Appsignal.instrument("process_action.sinatra") do
68
- status, headers, obody = @app.call(env)
69
- [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
61
+ @app.call(env)
70
62
  end
71
63
  rescue Exception => error # rubocop:disable Lint/RescueException
72
64
  transaction.set_error(error)
73
- complete_transaction_without_body = true
74
65
  raise error
75
66
  ensure
76
67
  # If raise_error is off versions of Sinatra don't raise errors, but store
@@ -82,10 +73,7 @@ module Appsignal
82
73
  transaction.set_metadata("path", request.path)
83
74
  transaction.set_metadata("method", request.request_method)
84
75
  transaction.set_http_or_background_queue_start
85
-
86
- # Transaction gets completed when the body gets read out, except in cases when
87
- # the app failed before returning us the Rack response triplet.
88
- Appsignal::Transaction.complete_current! if complete_transaction_without_body
76
+ Appsignal::Transaction.complete_current!
89
77
  end
90
78
  end
91
79
 
@@ -16,9 +16,7 @@ module Appsignal
16
16
  if Appsignal.active?
17
17
  call_with_appsignal_monitoring(env)
18
18
  else
19
- nil_transaction = Appsignal::Transaction::NilTransaction.new
20
- status, headers, obody = @app.call(env)
21
- [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
19
+ @app.call(env)
22
20
  end
23
21
  end
24
22
 
@@ -30,35 +28,46 @@ module Appsignal
30
28
  request
31
29
  )
32
30
 
33
- # We need to complete the transaction if there is an exception exception inside the `call`
34
- # of the app. If there isn't one and the app returns us a Rack response triplet, we let
35
- # the BodyWrapper complete the transaction when #close gets called on it
36
- # (guaranteed by the webserver)
37
- complete_transaction_without_body = false
38
-
39
31
  # Instrument a `process_action`, to set params/action name
40
- begin
32
+ status, headers, body =
41
33
  Appsignal.instrument("process_action.rack") do
42
- status, headers, obody = @app.call(env)
43
- [status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
34
+ @app.call(env)
35
+ rescue Exception => e # rubocop:disable Lint/RescueException
36
+ transaction.set_error(e)
37
+ raise e
38
+ ensure
39
+ transaction.set_action_if_nil(env["appsignal.action"])
40
+ transaction.set_metadata("path", request.path)
41
+ transaction.set_metadata("method", request.request_method)
42
+ transaction.set_http_or_background_queue_start
44
43
  end
45
- rescue Exception => error # rubocop:disable Lint/RescueException
46
- transaction.set_error(error)
47
- complete_transaction_without_body = true
48
- raise error
49
- ensure
50
- transaction.set_action_if_nil(env["appsignal.action"])
51
- transaction.set_metadata("path", request.path)
52
- transaction.set_metadata("method", request.request_method)
53
- transaction.set_http_or_background_queue_start
54
44
 
55
- # Transaction gets completed when the body gets read out, except in cases when
56
- # the app failed before returning us the Rack response triplet.
57
- Appsignal::Transaction.complete_current! if complete_transaction_without_body
58
- end
45
+ # Wrap the result body with our StreamWrapper
46
+ [status, headers, StreamWrapper.new(body, transaction)]
59
47
  end
60
48
  end
61
49
  end
62
50
 
63
- StreamWrapper = Rack::EnumerableBodyWrapper
51
+ class StreamWrapper
52
+ def initialize(stream, transaction)
53
+ @stream = stream
54
+ @transaction = transaction
55
+ end
56
+
57
+ def each(&block)
58
+ @stream.each(&block)
59
+ rescue Exception => e # rubocop:disable Lint/RescueException
60
+ @transaction.set_error(e)
61
+ raise e
62
+ end
63
+
64
+ def close
65
+ @stream.close if @stream.respond_to?(:close)
66
+ rescue Exception => e # rubocop:disable Lint/RescueException
67
+ @transaction.set_error(e)
68
+ raise e
69
+ ensure
70
+ Appsignal::Transaction.complete_current!
71
+ end
72
+ end
64
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "3.6.1"
4
+ VERSION = "3.6.2"
5
5
  end
data/lib/appsignal.rb CHANGED
@@ -305,6 +305,5 @@ require "appsignal/garbage_collection"
305
305
  require "appsignal/integrations/railtie" if defined?(::Rails)
306
306
  require "appsignal/transaction"
307
307
  require "appsignal/version"
308
- require "appsignal/rack/body_wrapper"
309
308
  require "appsignal/rack/generic_instrumentation"
310
309
  require "appsignal/transmitter"
@@ -50,7 +50,7 @@ describe Appsignal::Rack::GenericInstrumentation do
50
50
  expect(app).to receive(:call).with(env)
51
51
  end
52
52
 
53
- context "with an exception raised from call()", :error => true do
53
+ context "with an exception", :error => true do
54
54
  let(:error) { ExampleException }
55
55
  let(:app) do
56
56
  double.tap do |d|
@@ -58,9 +58,8 @@ describe Appsignal::Rack::GenericInstrumentation do
58
58
  end
59
59
  end
60
60
 
61
- it "records the exception and completes the transaction" do
61
+ it "records the exception" do
62
62
  expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
63
- expect(Appsignal::Transaction).to receive(:complete_current!)
64
63
  end
65
64
  end
66
65
 
@@ -65,8 +65,7 @@ if DependencyHelper.rails_present?
65
65
 
66
66
  describe "#call_with_appsignal_monitoring" do
67
67
  def run
68
- _status, _headers, body = middleware.call(env)
69
- body.close # Rack will always call close() on the body
68
+ middleware.call(env)
70
69
  end
71
70
 
72
71
  it "calls the wrapped app" do
@@ -127,8 +126,7 @@ if DependencyHelper.rails_present?
127
126
  end
128
127
  end
129
128
 
130
- it "records the exception and completes the transaction" do
131
- expect(Appsignal::Transaction).to receive(:complete_current!)
129
+ it "records the exception" do
132
130
  expect { run }.to raise_error(error)
133
131
 
134
132
  transaction_hash = last_transaction.to_h
@@ -3,9 +3,7 @@ if DependencyHelper.sinatra_present?
3
3
 
4
4
  module SinatraRequestHelpers
5
5
  def make_request(env)
6
- _status, _headers, body = middleware.call(env)
7
- # Close the body so that the transaction gets completed
8
- body&.close
6
+ middleware.call(env)
9
7
  end
10
8
 
11
9
  def make_request_with_error(env, error)
@@ -90,11 +90,10 @@ describe Appsignal::Rack::StreamingListener do
90
90
  context "with an exception in the instrumentation call" do
91
91
  let(:error) { ExampleException }
92
92
 
93
- it "should add the exception to the transaction and complete the transaction" do
93
+ it "should add the exception to the transaction" do
94
94
  allow(app).to receive(:call).and_raise(error)
95
95
 
96
96
  expect(transaction).to receive(:set_error).with(error)
97
- expect(Appsignal::Transaction).to receive(:complete_current!).and_call_original
98
97
 
99
98
  expect do
100
99
  listener.call_with_appsignal_monitoring(env)
@@ -102,19 +101,64 @@ describe Appsignal::Rack::StreamingListener do
102
101
  end
103
102
  end
104
103
 
105
- it "should wrap the response body in a wrapper" do
104
+ it "should wrap the body in a wrapper" do
105
+ expect(Appsignal::StreamWrapper).to receive(:new)
106
+ .with("body", transaction)
107
+ .and_return(wrapper)
108
+
106
109
  body = listener.call_with_appsignal_monitoring(env)[2]
107
110
 
108
- expect(body).to be_kind_of(Appsignal::Rack::BodyWrapper)
111
+ expect(body).to be_a(Appsignal::StreamWrapper)
109
112
  end
110
113
  end
111
114
  end
112
115
 
113
116
  describe Appsignal::StreamWrapper do
114
- it ".new returns an EnumerableWrapper" do
115
- fake_body = double(:each => nil)
116
- fake_txn = double
117
- stream_wrapper = Appsignal::StreamWrapper.new(fake_body, fake_txn)
118
- expect(stream_wrapper).to be_kind_of(Appsignal::Rack::EnumerableBodyWrapper)
117
+ let(:stream) { double }
118
+ let(:transaction) do
119
+ Appsignal::Transaction.create(SecureRandom.uuid, Appsignal::Transaction::HTTP_REQUEST, {})
120
+ end
121
+ let(:wrapper) { Appsignal::StreamWrapper.new(stream, transaction) }
122
+
123
+ describe "#each" do
124
+ it "calls the original stream" do
125
+ expect(stream).to receive(:each)
126
+
127
+ wrapper.each
128
+ end
129
+
130
+ context "when #each raises an error" do
131
+ let(:error) { ExampleException }
132
+
133
+ it "records the exception" do
134
+ allow(stream).to receive(:each).and_raise(error)
135
+
136
+ expect(transaction).to receive(:set_error).with(error)
137
+
138
+ expect { wrapper.send(:each) }.to raise_error(error)
139
+ end
140
+ end
141
+ end
142
+
143
+ describe "#close" do
144
+ it "closes the original stream and completes the transaction" do
145
+ expect(stream).to receive(:close)
146
+ expect(Appsignal::Transaction).to receive(:complete_current!)
147
+
148
+ wrapper.close
149
+ end
150
+
151
+ context "when #close raises an error" do
152
+ let(:error) { ExampleException }
153
+
154
+ it "records the exception and completes the transaction" do
155
+ allow(stream).to receive(:close).and_raise(error)
156
+
157
+ expect(transaction).to receive(:set_error).with(error)
158
+ expect(transaction).to receive(:complete)
159
+
160
+ expect { wrapper.send(:close) }.to raise_error(error)
161
+ end
162
+ end
119
163
  end
120
164
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.1
4
+ version: 3.6.2
5
5
  platform: java
6
6
  authors:
7
7
  - Robert Beekman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-03-05 00:00:00.000000000 Z
13
+ date: 2024-03-08 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -282,7 +282,6 @@ files:
282
282
  - lib/appsignal/probes/helpers.rb
283
283
  - lib/appsignal/probes/mri.rb
284
284
  - lib/appsignal/probes/sidekiq.rb
285
- - lib/appsignal/rack/body_wrapper.rb
286
285
  - lib/appsignal/rack/generic_instrumentation.rb
287
286
  - lib/appsignal/rack/rails_instrumentation.rb
288
287
  - lib/appsignal/rack/sinatra_instrumentation.rb
@@ -381,7 +380,6 @@ files:
381
380
  - spec/lib/appsignal/probes/gvl_spec.rb
382
381
  - spec/lib/appsignal/probes/mri_spec.rb
383
382
  - spec/lib/appsignal/probes/sidekiq_spec.rb
384
- - spec/lib/appsignal/rack/body_wrapper_spec.rb
385
383
  - spec/lib/appsignal/rack/generic_instrumentation_spec.rb
386
384
  - spec/lib/appsignal/rack/rails_instrumentation_spec.rb
387
385
  - spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb
@@ -467,7 +465,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
467
465
  - !ruby/object:Gem::Version
468
466
  version: '0'
469
467
  requirements: []
470
- rubygems_version: 3.4.15
468
+ rubygems_version: 3.3.7
471
469
  signing_key:
472
470
  specification_version: 4
473
471
  summary: Logs performance and exception data from your app to appsignal.com
@@ -1,161 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Appsignal
4
- # @api private
5
- module Rack
6
- class BodyWrapper
7
- def self.wrap(original_body, appsignal_transaction)
8
- # The logic of how Rack treats a response body differs based on which methods
9
- # the body responds to. This means that to support the Rack 3.x spec in full
10
- # we need to return a wrapper which matches the API of the wrapped body as closely
11
- # as possible. Pick the wrapper from the most specific to the least specific.
12
- # See https://github.com/rack/rack/blob/main/SPEC.rdoc#the-body-
13
- #
14
- # What is important is that our Body wrapper responds to the same methods Rack
15
- # (or a webserver) would be checking and calling, and passes through that functionality
16
- # to the original body. This can be done using delegation via i.e. SimpleDelegate
17
- # but we also need "close" to get called correctly so that the Appsignal transaction
18
- # gets completed - which will not happen, for example, when #to_ary gets called
19
- # just on the delegated Rack body.
20
- #
21
- # This comment https://github.com/rails/rails/pull/49627#issuecomment-1769802573
22
- # is of particular interest to understand why this has to be somewhat complicated.
23
- if original_body.respond_to?(:to_path)
24
- PathableBodyWrapper.new(original_body, appsignal_transaction)
25
- elsif original_body.respond_to?(:to_ary)
26
- ArrayableBodyWrapper.new(original_body, appsignal_transaction)
27
- elsif !original_body.respond_to?(:each) && original_body.respond_to?(:call)
28
- # This body only supports #call, so we must be running a Rack 3 application
29
- # It is possible that a body exposes both `each` and `call` in the hopes of
30
- # being backwards-compatible with both Rack 3.x and Rack 2.x, however
31
- # this is not going to work since the SPEC says that if both are available,
32
- # `each` should be used and `call` should be ignored.
33
- # So for that case we can drop by to our default EnumerableBodyWrapper
34
- CallableBodyWrapper.new(original_body, appsignal_transaction)
35
- else
36
- EnumerableBodyWrapper.new(original_body, appsignal_transaction)
37
- end
38
- end
39
-
40
- def initialize(body, appsignal_transaction)
41
- @body_already_closed = false
42
- @body = body
43
- @transaction = appsignal_transaction
44
- end
45
-
46
- # This must be present in all Rack bodies and will be called by the serving adapter
47
- def close
48
- # The @body_already_closed check is needed so that if `to_ary`
49
- # of the body has already closed itself (as prescribed) we do not
50
- # attempt to close it twice
51
- if !@body_already_closed && @body.respond_to?(:close)
52
- Appsignal.instrument("response_body_close.rack") { @body.close }
53
- end
54
- @body_already_closed = true
55
- rescue Exception => error # rubocop:disable Lint/RescueException
56
- @transaction.set_error(error)
57
- raise error
58
- ensure
59
- complete_transaction!
60
- end
61
-
62
- def complete_transaction!
63
- # We need to call the Transaction class method and not
64
- # @transaction.complete because the transaction is still
65
- # thread-local and it needs to remove itself from the
66
- # thread variables correctly, which does not happen on
67
- # Transaction#complete.
68
- #
69
- # In the future it would be a good idea to ensure
70
- # that the current transaction is the same as @transaction,
71
- # or allow @transaction to complete itself and remove
72
- # itself from Thread.current
73
- Appsignal::Transaction.complete_current!
74
- end
75
- end
76
-
77
- # The standard Rack body wrapper which exposes "each" for iterating
78
- # over the response body. This is supported across all 3 major Rack
79
- # versions.
80
- #
81
- # @api private
82
- class EnumerableBodyWrapper < BodyWrapper
83
- def each(&blk)
84
- # This is a workaround for the Rails bug when there was a bit too much
85
- # eagerness in implementing to_ary, see:
86
- # https://github.com/rails/rails/pull/44953
87
- # https://github.com/rails/rails/pull/47092
88
- # https://github.com/rails/rails/pull/49627
89
- # https://github.com/rails/rails/issues/49588
90
- # While the Rack SPEC does not mandate `each` to be callable
91
- # in a blockless way it is still a good idea to have it in place.
92
- return enum_for(:each) unless block_given?
93
-
94
- Appsignal.instrument("process_response_body.rack", "Process Rack response body (#each)") do
95
- @body.each(&blk)
96
- end
97
- rescue Exception => error # rubocop:disable Lint/RescueException
98
- @transaction.set_error(error)
99
- raise error
100
- end
101
- end
102
-
103
- # The callable response bodies are a new Rack 3.x feature, and would not work
104
- # with older Rack versions. They must not respond to `each` because
105
- # "If it responds to each, you must call each and not call". This is why
106
- # it inherits from BodyWrapper directly and not from EnumerableBodyWrapper
107
- #
108
- # @api private
109
- class CallableBodyWrapper < BodyWrapper
110
- def call(stream)
111
- # `stream` will be closed by the app we are calling, no need for us
112
- # to close it ourselves
113
- Appsignal.instrument("process_response_body.rack", "Process Rack response body (#call)") do
114
- @body.call(stream)
115
- end
116
- rescue Exception => error # rubocop:disable Lint/RescueException
117
- @transaction.set_error(error)
118
- raise error
119
- end
120
- end
121
-
122
- # "to_ary" takes precedence over "each" and allows the response body
123
- # to be read eagerly. If the body supports that method, it takes precedence
124
- # over "each":
125
- # "Middleware may call to_ary directly on the Body and return a new Body in its place"
126
- # One could "fold" both the to_ary API and the each() API into one Body object, but
127
- # to_ary must also call "close" after it executes - and in the Rails implementation
128
- # this pecularity was not handled properly.
129
- #
130
- # @api private
131
- class ArrayableBodyWrapper < EnumerableBodyWrapper
132
- def to_ary
133
- @body_already_closed = true
134
- Appsignal.instrument(
135
- "process_response_body.rack",
136
- "Process Rack response body (#to_ary)"
137
- ) do
138
- @body.to_ary
139
- end
140
- rescue Exception => error # rubocop:disable Lint/RescueException
141
- @transaction.set_error(error)
142
- raise error
143
- ensure
144
- # We do not call "close" on ourselves as the only action
145
- # we need to complete is completing the transaction.
146
- complete_transaction!
147
- end
148
- end
149
-
150
- # Having "to_path" on a body allows Rack to serve out a static file, or to
151
- # pass that file to the downstream webserver for sending using X-Sendfile
152
- class PathableBodyWrapper < EnumerableBodyWrapper
153
- def to_path
154
- Appsignal.instrument("response_body_to_path.rack") { @body.to_path }
155
- rescue Exception => error # rubocop:disable Lint/RescueException
156
- @transaction.set_error(error)
157
- raise error
158
- end
159
- end
160
- end
161
- end
@@ -1,220 +0,0 @@
1
- describe Appsignal::Rack::BodyWrapper do
2
- let(:nil_txn) { Appsignal::Transaction::NilTransaction.new }
3
-
4
- describe "with a body that supports all possible features" do
5
- it "reduces the supported methods to just each()" do
6
- # which is the safest thing to do, since the body is likely broken
7
- fake_body = double(:each => nil, :call => nil, :to_ary => [], :to_path => "/tmp/foo.bin",
8
- :close => nil)
9
- wrapped = described_class.wrap(fake_body, nil_txn)
10
- expect(wrapped).to respond_to(:each)
11
- expect(wrapped).not_to respond_to(:to_ary)
12
- expect(wrapped).not_to respond_to(:call)
13
- expect(wrapped).to respond_to(:close)
14
- end
15
- end
16
-
17
- describe "with a body only supporting each()" do
18
- it "wraps with appropriate class" do
19
- fake_body = double
20
- allow(fake_body).to receive(:each)
21
-
22
- wrapped = described_class.wrap(fake_body, nil_txn)
23
- expect(wrapped).to respond_to(:each)
24
- expect(wrapped).not_to respond_to(:to_ary)
25
- expect(wrapped).not_to respond_to(:call)
26
- expect(wrapped).to respond_to(:close)
27
- end
28
-
29
- it "reads out the body in full using each" do
30
- fake_body = double
31
- expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
32
- wrapped = described_class.wrap(fake_body, nil_txn)
33
- expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
34
- end
35
-
36
- it "returns an Enumerator if each() gets called without a block" do
37
- fake_body = double
38
- expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
39
-
40
- wrapped = described_class.wrap(fake_body, nil_txn)
41
- enum = wrapped.each
42
- expect(enum).to be_kind_of(Enumerator)
43
- expect { |b| enum.each(&b) }.to yield_successive_args("a", "b", "c")
44
- end
45
-
46
- it "sets the exception raised inside each() into the Appsignal transaction" do
47
- fake_body = double
48
- expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
49
-
50
- txn = double("Appsignal transaction", "nil_transaction?" => false)
51
- expect(txn).to receive(:set_error).once.with(instance_of(Exception))
52
-
53
- wrapped = described_class.wrap(fake_body, txn)
54
- expect do
55
- expect { |b| wrapped.each(&b) }.to yield_control
56
- end.to raise_error(/Oops/)
57
- end
58
-
59
- it "closes the body and the transaction when it gets closed" do
60
- fake_body = double
61
- expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
62
-
63
- txn = double("Appsignal transaction", "nil_transaction?" => false)
64
- expect(Appsignal::Transaction).to receive(:complete_current!).once
65
-
66
- wrapped = described_class.wrap(fake_body, txn)
67
- expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
68
- expect { wrapped.close }.not_to raise_error
69
- end
70
- end
71
-
72
- describe "with a body supporting both each() and call" do
73
- it "wraps with the wrapper that conceals call() and exposes each" do
74
- fake_body = double
75
- allow(fake_body).to receive(:each)
76
- allow(fake_body).to receive(:call)
77
-
78
- wrapped = described_class.wrap(fake_body, nil_txn)
79
- expect(wrapped).to respond_to(:each)
80
- expect(wrapped).not_to respond_to(:to_ary)
81
- expect(wrapped).not_to respond_to(:call)
82
- expect(wrapped).not_to respond_to(:to_path)
83
- expect(wrapped).to respond_to(:close)
84
- end
85
- end
86
-
87
- describe "with a body supporting both to_ary and each" do
88
- let(:fake_body) { double(:each => nil, :to_ary => []) }
89
- it "wraps with appropriate class" do
90
- wrapped = described_class.wrap(fake_body, nil_txn)
91
- expect(wrapped).to respond_to(:each)
92
- expect(wrapped).to respond_to(:to_ary)
93
- expect(wrapped).not_to respond_to(:call)
94
- expect(wrapped).not_to respond_to(:to_path)
95
- expect(wrapped).to respond_to(:close)
96
- end
97
-
98
- it "reads out the body in full using each" do
99
- expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
100
-
101
- wrapped = described_class.wrap(fake_body, nil_txn)
102
- expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
103
- end
104
-
105
- it "sets the exception raised inside each() into the Appsignal transaction" do
106
- expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
107
-
108
- txn = double("Appsignal transaction", "nil_transaction?" => false)
109
- expect(txn).to receive(:set_error).once.with(instance_of(Exception))
110
-
111
- wrapped = described_class.wrap(fake_body, txn)
112
- expect do
113
- expect { |b| wrapped.each(&b) }.to yield_control
114
- end.to raise_error(/Oops/)
115
- end
116
-
117
- it "reads out the body in full using to_ary" do
118
- expect(fake_body).to receive(:to_ary).and_return(["one", "two", "three"])
119
-
120
- wrapped = described_class.wrap(fake_body, nil_txn)
121
- expect(wrapped.to_ary).to eq(["one", "two", "three"])
122
- end
123
-
124
- it "sends the exception raised inside to_ary() into the Appsignal and closes txn" do
125
- fake_body = double
126
- allow(fake_body).to receive(:each)
127
- expect(fake_body).to receive(:to_ary).once.and_raise(Exception.new("Oops"))
128
- expect(fake_body).not_to receive(:close) # Per spec we expect the body has closed itself
129
-
130
- txn = double("Appsignal transaction", "nil_transaction?" => false)
131
- expect(txn).to receive(:set_error).once.with(instance_of(Exception))
132
- expect(Appsignal::Transaction).to receive(:complete_current!).once
133
-
134
- wrapped = described_class.wrap(fake_body, txn)
135
- expect { wrapped.to_ary }.to raise_error(/Oops/)
136
- end
137
- end
138
-
139
- describe "with a body supporting both to_path and each" do
140
- let(:fake_body) { double(:each => nil, :to_path => nil) }
141
-
142
- it "wraps with appropriate class" do
143
- wrapped = described_class.wrap(fake_body, nil_txn)
144
- expect(wrapped).to respond_to(:each)
145
- expect(wrapped).not_to respond_to(:to_ary)
146
- expect(wrapped).not_to respond_to(:call)
147
- expect(wrapped).to respond_to(:to_path)
148
- expect(wrapped).to respond_to(:close)
149
- end
150
-
151
- it "reads out the body in full using each()" do
152
- expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
153
-
154
- wrapped = described_class.wrap(fake_body, nil_txn)
155
- expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
156
- end
157
-
158
- it "sets the exception raised inside each() into the Appsignal transaction" do
159
- expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
160
-
161
- txn = double("Appsignal transaction", "nil_transaction?" => false)
162
- expect(txn).to receive(:set_error).once.with(instance_of(Exception))
163
-
164
- wrapped = described_class.wrap(fake_body, txn)
165
- expect do
166
- expect { |b| wrapped.each(&b) }.to yield_control
167
- end.to raise_error(/Oops/)
168
- end
169
-
170
- it "sets the exception raised inside to_path() into the Appsignal transaction" do
171
- allow(fake_body).to receive(:to_path).once.and_raise(Exception.new("Oops"))
172
-
173
- txn = double("Appsignal transaction", "nil_transaction?" => false)
174
- expect(txn).to receive(:set_error).once.with(instance_of(Exception))
175
- expect(txn).not_to receive(:complete) # gets called by the caller via close()
176
-
177
- wrapped = described_class.wrap(fake_body, txn)
178
- expect { wrapped.to_path }.to raise_error(/Oops/)
179
- end
180
-
181
- it "exposes to_path to the sender" do
182
- allow(fake_body).to receive(:to_path).and_return("/tmp/file.bin")
183
-
184
- wrapped = described_class.wrap(fake_body, nil_txn)
185
- expect(wrapped.to_path).to eq("/tmp/file.bin")
186
- end
187
- end
188
-
189
- describe "with a body only supporting call()" do
190
- let(:fake_body) { double(:call => nil) }
191
- it "wraps with appropriate class" do
192
- wrapped = described_class.wrap(fake_body, nil_txn)
193
- expect(wrapped).not_to respond_to(:each)
194
- expect(wrapped).not_to respond_to(:to_ary)
195
- expect(wrapped).to respond_to(:call)
196
- expect(wrapped).not_to respond_to(:to_path)
197
- expect(wrapped).to respond_to(:close)
198
- end
199
-
200
- it "passes the stream into the call() of the body" do
201
- fake_rack_stream = double("stream")
202
- expect(fake_body).to receive(:call).with(fake_rack_stream)
203
-
204
- wrapped = described_class.wrap(fake_body, nil_txn)
205
- expect { wrapped.call(fake_rack_stream) }.not_to raise_error
206
- end
207
-
208
- it "sets the exception raised inside call() into the Appsignal transaction" do
209
- fake_rack_stream = double
210
- allow(fake_body).to receive(:call).with(fake_rack_stream).and_raise(Exception.new("Oopsie"))
211
-
212
- txn = double("Appsignal transaction", "nil_transaction?" => false)
213
- expect(txn).to receive(:set_error).once.with(instance_of(Exception))
214
- expect(txn).not_to receive(:complete) # gets called by the caller via close()
215
- wrapped = described_class.wrap(fake_body, txn)
216
-
217
- expect { wrapped.call(fake_rack_stream) }.to raise_error(/Oopsie/)
218
- end
219
- end
220
- end