about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorAkihiko Odaki <akihiko.odaki.4i@stu.hosei.ac.jp>2018-03-24 20:49:54 +0900
committerEugen Rochko <eugen@zeonfederated.com>2018-03-24 12:49:54 +0100
commit54b273bf993888cd079113dd588cb7a90228b93b (patch)
treed6437a702618c9108ffad35a540a222a053b2c7d /app
parent4e71b104e6d5f02069120c7a56b26888c6f0fef5 (diff)
Close http connection in perform method of Request class (#6889)
HTTP connections must be explicitly closed in many cases, and letting
perform method close connections makes its callers less redundant and
prevent them from forgetting to close connections.
Diffstat (limited to 'app')
-rw-r--r--app/helpers/jsonld_helper.rb6
-rw-r--r--app/lib/provider_discovery.rb17
-rw-r--r--app/lib/request.rb16
-rw-r--r--app/models/concerns/remotable.rb34
-rw-r--r--app/services/fetch_atom_service.rb47
-rw-r--r--app/services/fetch_link_card_service.rb21
-rw-r--r--app/services/resolve_account_service.rb9
-rw-r--r--app/services/send_interaction_service.rb8
-rw-r--r--app/services/subscribe_service.rb36
-rw-r--r--app/services/unsubscribe_service.rb7
-rw-r--r--app/workers/activitypub/delivery_worker.rb15
-rw-r--r--app/workers/pubsubhubbub/confirmation_worker.rb18
-rw-r--r--app/workers/pubsubhubbub/delivery_worker.rb17
13 files changed, 126 insertions, 125 deletions
diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb
index 9530ad9f3..957a2cbc9 100644
--- a/app/helpers/jsonld_helper.rb
+++ b/app/helpers/jsonld_helper.rb
@@ -60,9 +60,9 @@ module JsonLdHelper
   end
 
   def fetch_resource_without_id_validation(uri)
-    response = build_request(uri).perform
-    return if response.code != 200
-    body_to_json(response.to_s)
+    build_request(uri).perform do |response|
+      response.code == 200 ? body_to_json(response.to_s) : nil
+    end
   end
 
   def body_to_json(body)
diff --git a/app/lib/provider_discovery.rb b/app/lib/provider_discovery.rb
index 5732e4fcb..bbd3a2d43 100644
--- a/app/lib/provider_discovery.rb
+++ b/app/lib/provider_discovery.rb
@@ -13,15 +13,14 @@ class ProviderDiscovery < OEmbed::ProviderDiscovery
     def discover_provider(url, **options)
       format = options[:format]
 
-      if options[:html]
-        html = Nokogiri::HTML(options[:html])
-      else
-        res = Request.new(:get, url).perform
-
-        raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html'
-
-        html = Nokogiri::HTML(res.to_s)
-      end
+      html = if options[:html]
+               Nokogiri::HTML(options[:html])
+             else
+               Request.new(:get, url).perform do |res|
+                 raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html'
+                 Nokogiri::HTML(res.to_s)
+               end
+             end
 
       if format.nil? || format == :json
         provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value
diff --git a/app/lib/request.rb b/app/lib/request.rb
index 298fb9528..8a127c65f 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -33,9 +33,17 @@ class Request
   end
 
   def perform
-    http_client.headers(headers).public_send(@verb, @url.to_s, @options)
-  rescue => e
-    raise e.class, "#{e.message} on #{@url}", e.backtrace[0]
+    begin
+      response = http_client.headers(headers).public_send(@verb, @url.to_s, @options)
+    rescue => e
+      raise e.class, "#{e.message} on #{@url}", e.backtrace[0]
+    end
+
+    begin
+      yield response
+    ensure
+      http_client.close
+    end
   end
 
   def headers
@@ -88,7 +96,7 @@ class Request
   end
 
   def http_client
-    HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
+    @http_client ||= HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
   end
 
   class Socket < TCPSocket
diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb
index 69685ec83..0f18c5d96 100644
--- a/app/models/concerns/remotable.rb
+++ b/app/models/concerns/remotable.rb
@@ -21,23 +21,23 @@ module Remotable
         return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.empty? || self[attribute_name] == url
 
         begin
-          response = Request.new(:get, url).perform
-
-          return if response.code != 200
-
-          matches  = response.headers['content-disposition']&.match(/filename="([^"]*)"/)
-          filename = matches.nil? ? parsed_url.path.split('/').last : matches[1]
-          basename = SecureRandom.hex(8)
-          extname = if filename.nil?
-                      ''
-                    else
-                      File.extname(filename)
-                    end
-
-          send("#{attachment_name}=", StringIO.new(response.to_s))
-          send("#{attachment_name}_file_name=", basename + extname)
-
-          self[attribute_name] = url if has_attribute?(attribute_name)
+          Request.new(:get, url).perform do |response|
+            next if response.code != 200
+
+            matches  = response.headers['content-disposition']&.match(/filename="([^"]*)"/)
+            filename = matches.nil? ? parsed_url.path.split('/').last : matches[1]
+            basename = SecureRandom.hex(8)
+            extname = if filename.nil?
+                        ''
+                      else
+                        File.extname(filename)
+                      end
+
+            send("#{attachment_name}=", StringIO.new(response.to_s))
+            send("#{attachment_name}_file_name=", basename + extname)
+
+            self[attribute_name] = url if has_attribute?(attribute_name)
+          end
         rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError => e
           Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
           nil
diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb
index c07859845..48ad5dcd3 100644
--- a/app/services/fetch_atom_service.rb
+++ b/app/services/fetch_atom_service.rb
@@ -24,43 +24,44 @@ class FetchAtomService < BaseService
 
   def process(url, terminal = false)
     @url = url
-    perform_request
-    process_response(terminal)
+    perform_request { |response| process_response(response, terminal) }
   end
 
-  def perform_request
+  def perform_request(&block)
     accept = 'text/html'
     accept = 'application/activity+json, application/ld+json, application/atom+xml, ' + accept unless @unsupported_activity
 
-    @response = Request.new(:get, @url)
-                       .add_headers('Accept' => accept)
-                       .perform
+    Request.new(:get, @url).add_headers('Accept' => accept).perform(&block)
   end
 
-  def process_response(terminal = false)
-    return nil if @response.code != 200
+  def process_response(response, terminal = false)
+    return nil if response.code != 200
 
-    if @response.mime_type == 'application/atom+xml'
-      [@url, { prefetched_body: @response.to_s }, :ostatus]
-    elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@response.mime_type)
-      json = body_to_json(@response.to_s)
+    if response.mime_type == 'application/atom+xml'
+      [@url, { prefetched_body: response.to_s }, :ostatus]
+    elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type)
+      json = body_to_json(response.to_s)
       if supported_context?(json) && json['type'] == 'Person' && json['inbox'].present?
-        [json['id'], { prefetched_body: @response.to_s, id: true }, :activitypub]
+        [json['id'], { prefetched_body: response.to_s, id: true }, :activitypub]
       elsif supported_context?(json) && json['type'] == 'Note'
-        [json['id'], { prefetched_body: @response.to_s, id: true }, :activitypub]
+        [json['id'], { prefetched_body: response.to_s, id: true }, :activitypub]
       else
         @unsupported_activity = true
         nil
       end
-    elsif @response['Link'] && !terminal && link_header.find_link(%w(rel alternate))
-      process_headers
-    elsif @response.mime_type == 'text/html' && !terminal
-      process_html
+    elsif !terminal
+      link_header = response['Link'] && parse_link_header(response)
+
+      if link_header&.find_link(%w(rel alternate))
+        process_link_headers(link_header)
+      elsif response.mime_type == 'text/html'
+        process_html(response)
+      end
     end
   end
 
-  def process_html
-    page = Nokogiri::HTML(@response.to_s)
+  def process_html(response)
+    page = Nokogiri::HTML(response.to_s)
 
     json_link = page.xpath('//link[@rel="alternate"]').find { |link| ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(link['type']) }
     atom_link = page.xpath('//link[@rel="alternate"]').find { |link| link['type'] == 'application/atom+xml' }
@@ -71,7 +72,7 @@ class FetchAtomService < BaseService
     result
   end
 
-  def process_headers
+  def process_link_headers(link_header)
     json_link = link_header.find_link(%w(rel alternate), %w(type application/activity+json)) || link_header.find_link(%w(rel alternate), ['type', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'])
     atom_link = link_header.find_link(%w(rel alternate), %w(type application/atom+xml))
 
@@ -81,7 +82,7 @@ class FetchAtomService < BaseService
     result
   end
 
-  def link_header
-    @link_header ||= LinkHeader.parse(@response['Link'].is_a?(Array) ? @response['Link'].first : @response['Link'])
+  def parse_link_header(response)
+    LinkHeader.parse(response['Link'].is_a?(Array) ? response['Link'].first : response['Link'])
   end
 end
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 8f252e64c..26deb5ecc 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -36,15 +36,24 @@ class FetchLinkCardService < BaseService
 
   def process_url
     @card ||= PreviewCard.new(url: @url)
-    res     = Request.new(:head, @url).perform
 
-    return if res.code != 405 && (res.code != 200 || res.mime_type != 'text/html')
+    failed = Request.new(:head, @url).perform do |res|
+      res.code != 405 && (res.code != 200 || res.mime_type != 'text/html')
+    end
 
-    @response = Request.new(:get, @url).perform
+    return if failed
 
-    return if @response.code != 200 || @response.mime_type != 'text/html'
+    Request.new(:get, @url).perform do |res|
+      if res.code == 200 && res.mime_type == 'text/html'
+        @html = res.to_s
+        @html_charset = res.charset
+      else
+        @html = nil
+        @html_charset = nil
+      end
+    end
 
-    @html = @response.to_s
+    return if @html.nil?
 
     attempt_oembed || attempt_opengraph
   end
@@ -118,7 +127,7 @@ class FetchLinkCardService < BaseService
     detector = CharlockHolmes::EncodingDetector.new
     detector.strip_tags = true
 
-    guess = detector.detect(@html, @response.charset)
+    guess = detector.detect(@html, @html_charset)
     page  = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil))
 
     if meta_property(page, 'twitter:player')
diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb
index fd6d30605..034821dc0 100644
--- a/app/services/resolve_account_service.rb
+++ b/app/services/resolve_account_service.rb
@@ -179,11 +179,10 @@ class ResolveAccountService < BaseService
   def atom_body
     return @atom_body if defined?(@atom_body)
 
-    response = Request.new(:get, atom_url).perform
-
-    raise Mastodon::UnexpectedResponseError, response unless response.code == 200
-
-    @atom_body = response.to_s
+    @atom_body = Request.new(:get, atom_url).perform do |response|
+      raise Mastodon::UnexpectedResponseError, response unless response.code == 200
+      response.to_s
+    end
   end
 
   def actor_json
diff --git a/app/services/send_interaction_service.rb b/app/services/send_interaction_service.rb
index fabba8a3e..3419043e5 100644
--- a/app/services/send_interaction_service.rb
+++ b/app/services/send_interaction_service.rb
@@ -12,11 +12,9 @@ class SendInteractionService < BaseService
 
     return if !target_account.ostatus? || block_notification?
 
-    delivery = build_request.perform
-
-    raise Mastodon::UnexpectedResponseError, delivery unless delivery.code > 199 && delivery.code < 300
-
-    delivery.connection&.close
+    build_request.perform do |delivery|
+      raise Mastodon::UnexpectedResponseError, delivery unless delivery.code > 199 && delivery.code < 300
+    end
   end
 
   private
diff --git a/app/services/subscribe_service.rb b/app/services/subscribe_service.rb
index 2f725e2ec..2893b5410 100644
--- a/app/services/subscribe_service.rb
+++ b/app/services/subscribe_service.rb
@@ -6,21 +6,21 @@ class SubscribeService < BaseService
 
     @account        = account
     @account.secret = SecureRandom.hex
-    @response       = build_request.perform
-
-    if response_failed_permanently?
-      # We're not allowed to subscribe. Fail and move on.
-      @account.secret = ''
-      @account.save!
-    elsif response_successful?
-      # The subscription will be confirmed asynchronously.
-      @account.save!
-    else
-      # The response was either a 429 rate limit, or a 5xx error.
-      # We need to retry at a later time. Fail loudly!
-      raise Mastodon::UnexpectedResponseError, @response
+
+    build_request.perform do |response|
+      if response_failed_permanently? response
+        # We're not allowed to subscribe. Fail and move on.
+        @account.secret = ''
+        @account.save!
+      elsif response_successful? response
+        # The subscription will be confirmed asynchronously.
+        @account.save!
+      else
+        # The response was either a 429 rate limit, or a 5xx error.
+        # We need to retry at a later time. Fail loudly!
+        raise Mastodon::UnexpectedResponseError, response
+      end
     end
-    @response.connection&.close
   end
 
   private
@@ -47,12 +47,12 @@ class SubscribeService < BaseService
   end
 
   # Any response in the 3xx or 4xx range, except for 429 (rate limit)
-  def response_failed_permanently?
-    (@response.status.redirect? || @response.status.client_error?) && !@response.status.too_many_requests?
+  def response_failed_permanently?(response)
+    (response.status.redirect? || response.status.client_error?) && !response.status.too_many_requests?
   end
 
   # Any response in the 2xx range
-  def response_successful?
-    @response.status.success?
+  def response_successful?(response)
+    response.status.success?
   end
 end
diff --git a/app/services/unsubscribe_service.rb b/app/services/unsubscribe_service.rb
index 01f5c6b7a..95c1fb4fc 100644
--- a/app/services/unsubscribe_service.rb
+++ b/app/services/unsubscribe_service.rb
@@ -7,10 +7,9 @@ class UnsubscribeService < BaseService
     @account = account
 
     begin
-      @response = build_request.perform
-
-      Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{@response.status}" unless @response.status.success?
-      @response.connection&.close
+      build_request.perform do |response|
+        Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{response.status}" unless response.status.success?
+      end
     rescue HTTP::Error, OpenSSL::SSL::SSLError => e
       Rails.logger.debug "PuSH unsubscribe for #{@account.acct} failed: #{e}"
     end
diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb
index 4763856ac..e6cfd0d07 100644
--- a/app/workers/activitypub/delivery_worker.rb
+++ b/app/workers/activitypub/delivery_worker.rb
@@ -12,11 +12,10 @@ class ActivityPub::DeliveryWorker
     @source_account = Account.find(source_account_id)
     @inbox_url      = inbox_url
 
-    perform_request
+    perform_request do |response|
+      raise Mastodon::UnexpectedResponseError, response unless response_successful? response
+    end
 
-    raise Mastodon::UnexpectedResponseError, @response unless response_successful?
-
-    @response.connection&.close
     failure_tracker.track_success!
   rescue => e
     failure_tracker.track_failure!
@@ -31,12 +30,12 @@ class ActivityPub::DeliveryWorker
     request.add_headers(HEADERS)
   end
 
-  def perform_request
-    @response = build_request.perform
+  def perform_request(&block)
+    build_request.perform(&block)
   end
 
-  def response_successful?
-    @response.code > 199 && @response.code < 300
+  def response_successful?(response)
+    response.code > 199 && response.code < 300
   end
 
   def failure_tracker
diff --git a/app/workers/pubsubhubbub/confirmation_worker.rb b/app/workers/pubsubhubbub/confirmation_worker.rb
index e1ccfb99c..cc2d1225b 100644
--- a/app/workers/pubsubhubbub/confirmation_worker.rb
+++ b/app/workers/pubsubhubbub/confirmation_worker.rb
@@ -21,8 +21,8 @@ class Pubsubhubbub::ConfirmationWorker
   def process_confirmation
     prepare_subscription
 
-    confirm_callback
-    logger.debug "Confirming PuSH subscription for #{subscription.callback_url} with challenge #{challenge}: #{callback_response_body}"
+    callback_get_with_params
+    logger.debug "Confirming PuSH subscription for #{subscription.callback_url} with challenge #{challenge}: #{@callback_response_body}"
 
     update_subscription
   end
@@ -44,7 +44,7 @@ class Pubsubhubbub::ConfirmationWorker
   end
 
   def response_matches_challenge?
-    callback_response_body == challenge
+    @callback_response_body == challenge
   end
 
   def subscribing?
@@ -55,16 +55,10 @@ class Pubsubhubbub::ConfirmationWorker
     mode == 'unsubscribe'
   end
 
-  def confirm_callback
-    @_confirm_callback ||= callback_get_with_params
-  end
-
   def callback_get_with_params
-    Request.new(:get, subscription.callback_url, params: callback_params).perform
-  end
-
-  def callback_response_body
-    confirm_callback.body.to_s
+    Request.new(:get, subscription.callback_url, params: callback_params).perform do |response|
+      @callback_response_body = response.body.to_s
+    end
   end
 
   def callback_params
diff --git a/app/workers/pubsubhubbub/delivery_worker.rb b/app/workers/pubsubhubbub/delivery_worker.rb
index a9174edd2..619bfa48a 100644
--- a/app/workers/pubsubhubbub/delivery_worker.rb
+++ b/app/workers/pubsubhubbub/delivery_worker.rb
@@ -23,22 +23,17 @@ class Pubsubhubbub::DeliveryWorker
   private
 
   def process_delivery
-    payload_delivery
+    callback_post_payload do |payload_delivery|
+      raise Mastodon::UnexpectedResponseError, payload_delivery unless response_successful? payload_delivery
+    end
 
-    raise Mastodon::UnexpectedResponseError, payload_delivery unless response_successful?
-
-    payload_delivery.connection&.close
     subscription.touch(:last_successful_delivery_at)
   end
 
-  def payload_delivery
-    @_payload_delivery ||= callback_post_payload
-  end
-
-  def callback_post_payload
+  def callback_post_payload(&block)
     request = Request.new(:post, subscription.callback_url, body: payload)
     request.add_headers(headers)
-    request.perform
+    request.perform(&block)
   end
 
   def blocked_domain?
@@ -80,7 +75,7 @@ class Pubsubhubbub::DeliveryWorker
     OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), subscription.secret, payload)
   end
 
-  def response_successful?
+  def response_successful?(payload_delivery)
     payload_delivery.code > 199 && payload_delivery.code < 300
   end
 end