lib/deepl/requests/base.rb in deepl-rb-2.5.3 vs lib/deepl/requests/base.rb in deepl-rb-3.0.0

- old
+ new

@@ -1,21 +1,31 @@ +# Copyright 2018 Daniel Herzog +# Use of this source code is governed by an MIT +# license that can be found in the LICENSE.md file. # frozen_string_literal: true module DeepL module Requests - class Base + class Base # rubocop:disable Metrics/ClassLength attr_reader :api, :response, :options - def initialize(api, options = {}) + def initialize(api, options = {}, additional_headers = {}) @api = api @options = options + @additional_headers = additional_headers + @num_retries = 0 + @backoff_timer = Utils::BackoffTimer.new end def request raise NotImplementedError end + def details + "HTTP Headers #{headers}" + end + private def option?(name) options.key?(name.to_s) || options.key?(name.to_sym) end @@ -34,47 +44,85 @@ else options[name.to_s] = value end end - def post(payload) - request = Net::HTTP::Post.new(uri.request_uri, headers) - request.set_form_data(payload.compact) - response = http.request(request) + # Files to reset: list of file objects to rewind when retrying the request + def execute_request_with_retries(req, files_to_reset = []) # rubocop:disable all + api.configuration.logger&.info("Request to the DeepL API: #{self}") + api.configuration.logger&.debug("Request details: #{details}") + loop do + resp = api.http_client.request(req) + validate_response!(resp) + return [req, resp] + rescue DeepL::Exceptions::Error => e + raise e unless should_retry?(resp, e, @backoff_timer.num_retries) - validate_response!(request, response) - [request, response] + unless e.nil? + api.configuration.logger&.info("Encountered a retryable exception: #{e.message}") + end + api.configuration.logger&.info("Starting retry #{@backoff_timer.num_retries + 1} for " \ + "request #{request} after sleeping for " \ + "#{format('%.2f', @backoff_timer.time_until_deadline)}") + files_to_reset.each(&:rewind) + @backoff_timer.sleep_until_deadline + next + rescue Net::HTTPBadResponse, Net::HTTPServerError, Net::HTTPFatalError, Timeout::Error, + SocketError => e + unless e.nil? + api.configuration.logger&.info("Encountered a retryable exception: #{e.message}") + end + api.configuration.logger&.info("Starting retry #{@backoff_timer.num_retries + 1} for " \ + "request #{request} after sleeping for " \ + "#{format('%.2f', @backoff_timer.time_until_deadline)}") + files_to_reset.each(&:rewind) + @backoff_timer.sleep_until_deadline + next + end end - def get - request = Net::HTTP::Get.new(uri.request_uri, headers) - response = http.request(request) + def should_retry?(response, exception, num_retries) + return false if num_retries >= api.configuration.max_network_retries + return exception.should_retry? if response.nil? - validate_response!(request, response) - [request, response] + response.is_a?(Net::HTTPTooManyRequests) || response.is_a?(Net::HTTPInternalServerError) end - def delete - request = Net::HTTP::Delete.new(uri.request_uri, headers) - response = http.request(request) + def post_request(payload) + http_headers = add_json_content_type(headers) + post_req = Net::HTTP::Post.new(uri.path, http_headers) + post_req.body = payload.merge(options).to_json + post_req + end - validate_response!(request, response) - [request, response] + def post_request_with_file(form_data) + http_headers = add_multipart_form_content_type(headers) + post_req = Net::HTTP::Post.new(uri.request_uri, http_headers) + # options are passed in `form_data` + form_data += options.map { |key, value| [key.to_s, value.to_s] } + post_req.set_form(form_data, 'multipart/form-data') + post_req end - def http - @http ||= begin - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = uri.scheme == 'https' - http - end + def get_request # rubocop:disable Naming/AccessorMethodName + http_headers = add_json_content_type(headers) + get_req = Net::HTTP::Get.new(uri.path, http_headers) + get_req.body = options.to_json + get_req end - def validate_response!(request, response) + def delete_request + http_headers = add_json_content_type(headers) + del_req = Net::HTTP::Delete.new(uri.path, http_headers) + del_req.body = options.to_json + del_req + end + + def validate_response!(response) return if response.is_a?(Net::HTTPSuccess) - raise Utils::ExceptionBuilder.new(request, response).build + raise Utils::ExceptionBuilder.new(response).build end def path raise NotImplementedError end @@ -82,16 +130,12 @@ def url "#{host}/#{api.configuration.version}/#{path}" end def uri - @uri ||= begin - uri = URI(url) - new_query = URI.decode_www_form(uri.query || '') + query_params.to_a - uri.query = URI.encode_www_form(new_query) - uri - end + @uri ||= URI(url) + @uri end def host api.configuration.host end @@ -99,10 +143,21 @@ def query_params options end def headers - { 'Authorization' => "DeepL-Auth-Key #{api.configuration.auth_key}" } + { 'Authorization' => "DeepL-Auth-Key #{api.configuration.auth_key}", + 'User-Agent' => api.configuration.user_agent }.merge(@additional_headers) + end + + def add_json_content_type(headers_to_add_to) + headers_to_add_to['Content-Type'] = 'application/json' + headers_to_add_to + end + + def add_multipart_form_content_type(headers_to_add_to) + headers_to_add_to['Content-Type'] = 'multipart/form-data' + headers_to_add_to end end end end