Надежный вызов flaky API: правильная обработка ошибок с помощью Net:: HTTP

Я взломал это вместе как, казалось бы, надежный способ называть сломанный веб-сервис, который давал таймауты и случайное разрешение имен или сокетную ошибку или что-то еще. Я думал, что положу это здесь, если это будет полезно или, скорее, будет сказано, что это лучший способ сделать это.

require 'net/http'

retries = 5
begin
  url = URI.parse('http://api.flakywebservice.com')
  http = Net::HTTP.new(url.host, url.port)
  http.read_timeout = 600  # be very patient
  res = nil
  http.start{|http|
    req = Net::HTTP::Post.new(url.path)
    req.set_form_data(params)  # send a hash of the POST parameters
    res = http.request(req)
  }
rescue Exception   # should really list all the possible http exceptions
  sleep 3
  retry if (retries -= 1) > 0
end

# finally, do something with res.body, like JSON.parse(res.body)

Сердце этого вопроса: Какие все исключения я должен искать при звонке в веб-сервис, как это? Вот попытка собрать их всех, но похоже, что это лучший способ: http://tammersaleh.com/posts/rescuing-net-http-exceptions

Ответ 1

Исключения имеют смысл, а Net::HTTP предлагает специальные исключения для разных типов случаев. Поэтому, если вы хотите обрабатывать их каждый особым образом, вы можете.

В этой статье говорится, что обработка этих конкретных исключений лучше/безопаснее, чем обработка rescue Exception, и это очень верно. НО, rescue Exception само по себе отличается от rescue, что эквивалентно rescue StandardError, что вы обычно делаете по умолчанию, если у вас нет причин делать что-либо еще.

Спасение верхнего уровня Exception спасет все, что может произойти во всем стеке выполнения, включая часть рубина, заканчивающуюся на диске или в памяти, или с некоторой неясной проблемой ввода-вывода, связанной с системой.

Итак, что касается "того, что нужно спасать", вам, как правило, лучше, если вы измените код на rescue. Вы поймаете все, что захотите, и ничего, чего вы не хотите. Однако в этом конкретном случае в списке парней есть одно исключение, которое НЕ является потомком StandardError:

def parents(obj)
  ( (obj.superclass ? parents(obj.superclass) : []) << obj)
end

[Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse,
  Net::HTTPHeaderSyntaxError, Net::ProtocolError].inject([]) do |a,c|
  parents(c).include?(StandardError) ? a : a << c
end
# Timeout::Error < Interrupt

parents(Timeout::Error)
# [ Object, Exception < Object, SignalException < Exception,
#   Interrupt < SignalException, Timeout::Error < Interrupt ]

Итак, вы можете изменить свой код на rescue StandardError, Timeout::Error => e, и вы покроете все случаи, упомянутые в этой статье, и многое другое, но не то, что вы не хотите покрывать. (=> e не требуется, но подробнее об этом ниже).

Теперь, насколько ваш фактический метод работы с flakey API - вопрос в том, с чем связана проблема с API, с которой вы имеете дело? Плохо отформатированные ответы? Нет Ответов? Является ли проблема на уровне HTTP или в данных, которые вы возвращаете?

Возможно, вы еще не знаете, или вам все равно, но вы знаете, что повторная попытка имеет тенденцию выполнять свою работу. В этом случае я бы по крайней мере рекомендовал регистрировать исключения. Hoptoad имеет бесплатный план и имеет нечто вроде Hoptoad.notify(e) - я не могу вспомнить, если это точный вызов. Или вы можете отправить его по электронной почте или зарегистрировать, используя e.message и e.stacktrace.