Потрібно повернути помилку 404 у форматі JSON у Rails


77

У мене є звичайний інтерфейс HTML та API JSON у моєму додатку Rails. Тепер, якщо хтось зателефонує, /api/not_existent_method.jsonвін повертає сторінку HTML 404 за замовчуванням. Чи є спосіб змінити це на щось подібне {"error": "not_found"}, залишаючи непорушною оригінальну сторінку 404 для інтерфейсу HTML?

Відповіді:


115

Друг вказав мені на елегантне рішення, яке не лише обробляє 404, а й 500 помилок. Насправді він обробляє кожну помилку. Ключовим є те, що кожна помилка генерує виняток, який поширюється вгору через стек проміжних засобів стійки, доки її не обробить одна з них. Якщо вам цікаво дізнатись більше, ви можете переглянути цей чудовий скринкаст . У Rails є власні обробники для винятків, але ви можете замінити їх менш документально підтвердженою exceptions_appопцією налаштування. Тепер ви можете написати власне проміжне програмне забезпечення або ж направити помилку назад у рейки, наприклад:

# In your config/application.rb
config.exceptions_app = self.routes

Тоді вам просто потрібно узгодити ці маршрути у вашому config/routes.rb:

get "/404" => "errors#not_found"
get "/500" => "errors#exception"

І тоді ви просто створюєте контролер для обробки цього.

class ErrorsController < ActionController::Base
  def not_found
    if env["REQUEST_PATH"] =~ /^\/api/
      render :json => {:error => "not-found"}.to_json, :status => 404
    else
      render :text => "404 Not found", :status => 404 # You can render your own template here
    end
  end

  def exception
    if env["REQUEST_PATH"] =~ /^\/api/
      render :json => {:error => "internal-server-error"}.to_json, :status => 500
    else
      render :text => "500 Internal Server Error", :status => 500 # You can render your own template here
    end
  end
end

Останнє, що слід додати: у середовищі розробки rails звичайно не відображає 404 або 500 сторінок, а натомість друкує зворотну трасування. Якщо ви хочете бачити свою ErrorsControllerдію в режимі розробки, вимкніть елементи зворотного відстеження у вашому config/enviroments/development.rbфайлі.

config.consider_all_requests_local = false

4
Також не забудьте додати код стану до візуалізації. В іншому випадку ваш клієнт / браузер не знатиме, що це був 404/500. render: text => "404 не знайдено"
,:

1
Я б сказав, що блок response_to є більш універсальним у функціях візуалізації: response_to do | format | format.json {render json: {error: "not-found"}. to_json, status: 404} format.html {render text: "404 Not found", status: 404} end
Грег Фунтусов

1
Я розставив імена своїх контролерів API і викинув виняток, який має базовий клас, такий як ApiException, який потім можна врятувати в базовому контролері Api з простором імен, де помилка JSON може бути повернута з відповідним статусом, як вище.
Річард Холліс,

@RichardHollis Мої контролери API також мають простір імен, але чи досягну я того, що ви зробили?
darksky

2
Перевірте цю відповідь, перш ніж вдаватися до запропонованого вище рішення: stackoverflow.com/a/29292738/2859525 . Набагато простіше просто вловити 404 у контролері предків і використовувати зворотний виклик, щоб зробити просту відповідь на помилку JSON.
Тодд,

14

Мені подобається створювати окремий контролер API, який встановлює формат (json) та специфічні методи api:

class ApiController < ApplicationController
  respond_to :json

  rescue_from ActiveRecord::RecordNotFound, with: :not_found
  # Use Mongoid::Errors::DocumentNotFound with mongoid

  def not_found
    respond_with '{"error": "not_found"}', status: :not_found
  end
end

Тест RSpec:

  it 'should return 404' do
    get "/api/route/specific/to/your/app/", format: :json
    expect(response.status).to eq(404)
  end

3
Здається, це працює лише для записів. Як би ви керували справою api/non_existant_route?
Себастіалонсо,

1
рядок rescue_from ActiveRecord::RecordNotFound, with: not_foundповинен бути, with: :not_foundале це лише один символ редагування: P
помилково

11

Звичайно, це буде виглядати приблизно так:

class ApplicationController < ActionController::Base
  rescue_from NotFoundException, :with => :not_found
  ...

  def not_found
    respond_to do |format|
      format.html { render :file => File.join(Rails.root, 'public', '404.html') }
      format.json { render :text => '{"error": "not_found"}' }
    end
  end
end

NotFoundExceptionце НЕ справжнє ім'я виключення. Це буде залежати від версії Rails та точної поведінки, яку ви хочете. Досить легко знайти за допомогою пошуку Google.


2
Дякую за ідею, але я використовую Rails 3.2.2, де обробка винятків змінилася. Це вже не буде працювати.
iblue

@iblue Це чудово працює в Rails 3.2+ і є кращим рішенням. Докладнішу інформацію див. У rescue_from документації .
Intelekshual

5

Спробуйте поставити в кінці з ваших routes.rb:

match '*foo', :format => true, :constraints => {:format => :json}, :to => lambda {|env| [404, {}, ['{"error": "not_found"}']] }

1
Для всіх, хто читає це, matchу Rails.application.routesфайлі тепер потрібно вказати метод HTTP через via:параметр. Це також відображатиметься як помилка при спробі використати наведений вище рядок.
sameers
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.