diff --git a/.ruby-version b/.ruby-version index 57cf282..a4dd9db 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.5 +2.7.4 diff --git a/.travis.yml b/.travis.yml index 7e3596a..9145582 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,5 @@ sudo: false language: ruby cache: bundler rvm: - - 2.6.3 -before_install: gem install bundler -v 1.17.3 + - 2.7.4 +before_install: gem install bundler -v 2.2.15 diff --git a/Gemfile.lock b/Gemfile.lock index e554bb9..2b88f93 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,7 @@ PATH remote: . specs: - emailable (3.0.0) - faraday - faraday_middleware - net-http-persistent + emailable (3.0.2) GEM remote: https://rubygems.org/ @@ -22,14 +19,6 @@ GEM builder (3.2.3) coderay (1.1.2) concurrent-ruby (1.1.7) - connection_pool (2.2.3) - faraday (1.3.0) - faraday-net_http (~> 1.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords - faraday-net_http (1.0.1) - faraday_middleware (1.0.0) - faraday (~> 1.0) i18n (1.8.5) concurrent-ruby (~> 1.0) method_source (0.9.2) @@ -39,15 +28,11 @@ GEM builder minitest (>= 5.0) ruby-progressbar - multipart-post (2.1.1) - net-http-persistent (4.0.1) - connection_pool (~> 2.2) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) rake (13.0.1) ruby-progressbar (1.10.1) - ruby2_keywords (0.0.4) thread_safe (0.3.6) tzinfo (1.2.8) thread_safe (~> 0.1) @@ -67,4 +52,4 @@ DEPENDENCIES rake (~> 13.0) BUNDLED WITH - 1.17.3 + 2.1.4 diff --git a/LICENSE.txt b/LICENSE.txt index 9495720..adb338e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 Emailable. https://emailable.com +Copyright (c) 2021 Emailable. https://emailable.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/emailable.gemspec b/emailable.gemspec index 91d7f7c..165c25e 100644 --- a/emailable.gemspec +++ b/emailable.gemspec @@ -27,9 +27,6 @@ Gem::Specification.new do |s| end s.require_paths = ['lib'] - s.add_dependency 'faraday' - s.add_dependency 'faraday_middleware' - s.add_dependency 'net-http-persistent' s.add_development_dependency 'bundler' s.add_development_dependency 'rake', '~> 13.0' s.add_development_dependency 'pry' diff --git a/lib/emailable.rb b/lib/emailable.rb index 4a90cd6..c3e698f 100644 --- a/lib/emailable.rb +++ b/lib/emailable.rb @@ -1,7 +1,6 @@ -require 'faraday' -require 'faraday_middleware' require 'emailable/version' require 'emailable/client' +require 'emailable/response' require 'emailable/batch' require 'emailable/resources/api_resource' require 'emailable/resources/account' @@ -14,9 +13,13 @@ module Emailable @max_network_retries = 1 + @open_timeout = 30 + @read_timeout = 60 + @write_timeout = 30 class << self - attr_accessor :api_key, :max_network_retries + attr_accessor :api_key, :max_network_retries, :open_timeout, :read_timeout, + :write_timeout end module_function diff --git a/lib/emailable/batch.rb b/lib/emailable/batch.rb index 1fdaf17..85e010d 100644 --- a/lib/emailable/batch.rb +++ b/lib/emailable/batch.rb @@ -4,15 +4,19 @@ class Batch def initialize(id_or_emails, callback: nil) if id_or_emails.is_a?(Array) + @id = nil @emails = id_or_emails @callback = callback elsif id_or_emails.is_a?(String) @id = id_or_emails + @emails = nil + @callback = nil else raise ArgumentError, 'expected an array of emails or batch id' end @client = Emailable::Client.new + @status = nil end def verify diff --git a/lib/emailable/client.rb b/lib/emailable/client.rb index d356802..bf5e989 100644 --- a/lib/emailable/client.rb +++ b/lib/emailable/client.rb @@ -2,27 +2,30 @@ module Emailable class Client def initialize - @client = Faraday.new('https://api.emailable.com/v1') do |f| - f.request :url_encoded - f.response :json, content_type: /\bjson$/ - f.adapter :net_http_persistent - end + @base_url = 'https://api.emailable.com/v1' + @connection = create_connection(URI(@base_url)) end - def request(method, endpoint, opts = {}) + def request(method, endpoint, params = {}) begin - tries ||= 0 + tries ||= 3 - @client.params[:api_key] = Emailable.api_key + uri = URI("#{@base_url}/#{endpoint}") + params[:api_key] = Emailable.api_key - response = + http_response = if method == :get - @client.get(endpoint, opts) + uri.query = URI.encode_www_form(params) + @connection.get(uri) elsif method == :post - @client.post(endpoint, opts) + request = Net::HTTP::Post.new(uri, 'Content-Type': 'application/json') + request.body = params.to_json + @connection.request(request) end + + response = Response.new(http_response) rescue => e - retry if self.class.should_retry?(e, tries) + retry if (tries -= 1) > 0 && self.class.should_retry?(e, tries) raise e end @@ -48,23 +51,59 @@ def request(method, endpoint, opts = {}) raise error_map[status.to_s].new(error_attributes) end + private + + def create_connection(uri) + connection = Net::HTTP.new(uri.host, uri.port) + + # Time in seconds within which Net::HTTP will try to reuse an already + # open connection when issuing a new operation. Outside this window, Ruby + # will transparently close and re-open the connection without trying to + # reuse it. + # + # Ruby's default of 2 seconds is almost certainly too short. Here I've + # reused Go's default for `DefaultTransport`. + connection.keep_alive_timeout = 30 + + connection.open_timeout = Emailable.open_timeout + connection.read_timeout = Emailable.read_timeout + if connection.respond_to?(:write_timeout=) + connection.write_timeout = Emailable.write_timeout + end + connection.use_ssl = uri.scheme == "https" + + connection + end + def self.should_retry?(error, num_retries) return false if num_retries >= Emailable.max_network_retries - # Retry on timeout-related problems (either on open or read). - return true if error.is_a?(Faraday::TimeoutError) + case error + when Net::OpenTimeout, Net::ReadTimeout + # Retry on timeout-related problems (either on open or read). + true + when EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET, + Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError + # Destination refused the connection, the connection was reset, or a + # variety of other connection failures. This could occur from a single + # saturated server, so retry in case it's intermittent. + true + when Net::HTTPError + # 409 Conflict + return true if error.http_status == 409 - # Destination refused the connection, the connection was reset, or a - # variety of other connection failures. This could occur from a single - # saturated server, so retry in case it's intermittent. - return true if error.is_a?(Faraday::ConnectionFailed) + # 429 Too Many Requests + return true if error.http_status == 429 - if error.is_a?(Faraday::ClientError) && error.response - # 409 conflict - return true if error.response[:status] == 409 - end + # 500 Internal Server Error + return true if error.http_status == 500 - false + # 503 Service Unavailable + error.http_status == 503 + else + false + end end + end end diff --git a/lib/emailable/email_validator.rb b/lib/emailable/email_validator.rb index 22e14e7..78c68e5 100644 --- a/lib/emailable/email_validator.rb +++ b/lib/emailable/email_validator.rb @@ -38,7 +38,7 @@ def validate_each(record, attribute, value) api_options = { timeout: timeout, smtp: smtp } api_options[:accept_all] = true unless accept_all - ev = Emailable.verify(value, api_options) + ev = Emailable.verify(value, **api_options) result_accessor = "#{attribute}_verification_result" if record.respond_to?(result_accessor) diff --git a/lib/emailable/response.rb b/lib/emailable/response.rb new file mode 100644 index 0000000..064e5c0 --- /dev/null +++ b/lib/emailable/response.rb @@ -0,0 +1,12 @@ +module Emailable + class Response + + attr_accessor :status, :body + + def initialize(response) + @status = response.code.to_i + @body = JSON.parse(response.body) + end + + end +end diff --git a/lib/emailable/version.rb b/lib/emailable/version.rb index 5dd666b..efe938a 100644 --- a/lib/emailable/version.rb +++ b/lib/emailable/version.rb @@ -1,3 +1,3 @@ module Emailable - VERSION = '3.0.1' + VERSION = '3.0.2' end