Ruby/Rails网络请求如何优雅捕获所有类型错误?
Great question—dealing with all the edge cases of HTTP requests in Rails can feel like herding cats, especially when you're repeating error-handling code everywhere. Let's break down a clean, DRY approach that covers both network-level errors and response validation issues.
The first step to DRYing up your code is to move all HTTP request logic into a dedicated service object. This way, you only write your error-handling code once, and every part of your app uses the same implementation.
Let's use Faraday (a popular HTTP client for Ruby) as an example, but this pattern works with Net::HTTP or any other client too:
# app/services/http_service.rb class HttpService class RequestError < StandardError; end class ResponseValidationError < StandardError; end def self.get(url, params = {}) perform_request(:get, url, params) end def self.post(url, body = {}) perform_request(:post, url, body) end private def self.perform_request(method, url, payload) connection = Faraday.new(url: url) do |faraday| faraday.response :json # Auto-parse JSON responses faraday.adapter Faraday.default_adapter end response = connection.public_send(method) do |req| req.params = payload if method == :get req.body = payload if method == :post end # Validate response structure after successful request validate_response(response) { success: true, data: response.body } rescue Faraday::NotFoundError => e handle_error("Resource not found (404): #{e.message}") rescue Faraday::TimeoutError, Timeout::Error, SocketError => e handle_error("Network timeout or connection failure: #{e.message}") rescue Faraday::Error => e handle_error("HTTP request failed: #{e.message}") rescue StandardError => e handle_error("Unexpected error: #{e.message}") end def self.validate_response(response) # Example: Check if required fields exist in the response required_fields = [:id, :name] # Adjust to your app's needs missing_fields = required_fields.select { |field| response.body[field].nil? } unless missing_fields.empty? raise ResponseValidationError, "Missing required fields: #{missing_fields.join(', ')}" end end def self.handle_error(message) Rails.logger.error("[HttpService] #{message}") { success: false, error: message } end end
Instead of returning a hash, you can create a simple Result class to make success/error handling even clearer. This eliminates the need to check hash keys everywhere:
# app/services/result.rb class Result attr_reader :success, :data, :error def initialize(success:, data: nil, error: nil) @success = success @data = data @error = error end def self.success(data) new(success: true, data: data) end def self.failure(error) new(success: false, error: error) end end
Then update the perform_request and handle_error methods in HttpService to return Result instances:
# In HttpService#perform_request Result.success(response.body) # In HttpService#handle_error Result.failure(message)
Now, whenever you need to make an HTTP request, just call the service, and handle the result cleanly:
result = HttpService.get("https://api.example.com/users", id: 123) if result.success? # Do something with the valid response data render json: result.data else # Handle error uniformly across your app render json: { error: result.error }, status: :bad_request end
- DRY: No more repeating rescue blocks across controllers/jobs. All error handling lives in one maintainable place.
- Comprehensive Coverage: Catches network timeouts, 404s, 500s, connection errors, and even unexpected edge cases via the
StandardErrorfallback. - Response Validation: Ensures the response has all the fields you need, turning silent failures (like missing data) into explicit, actionable errors.
- Consistent Interface: Callers only need to check
success?instead of handling a dozen different exception types.
You can extend this further by adding more specific custom exceptions (like RateLimitError if you hit API rate limits) or adjusting the validation logic to match your app's unique data requirements.
内容的提问来源于stack exchange,提问作者arjun

