Es muss ein JSON-formatierter 404-Fehler in Rails zurückgegeben werden

77

Ich habe ein normales HTML-Frontend und eine JSON-API in meiner Rails-App. Wenn jemand anruft /api/not_existent_method.json, wird die Standard-HTML 404-Seite zurückgegeben. Gibt es eine Möglichkeit, dies in etwas zu ändern, {"error": "not_found"}während die ursprüngliche 404-Seite für das HTML-Frontend intakt bleibt?

iblue
quelle

Antworten:

114

Ein Freund wies mich auf eine elegante Lösung hin, die nicht nur 404, sondern auch 500 Fehler behandelt. Tatsächlich behandelt es jeden Fehler. Der Schlüssel ist, dass jeder Fehler eine Ausnahme generiert, die sich durch den Stapel von Rack-Middlewares nach oben ausbreitet, bis sie von einem von ihnen behandelt wird. Wenn Sie mehr erfahren möchten, können Sie sich diesen hervorragenden Screencast ansehen . Rails verfügt über eigene Handler für Ausnahmen, die Sie jedoch mit der weniger dokumentierten exceptions_appKonfigurationsoption überschreiben können . Jetzt können Sie Ihre eigene Middleware schreiben oder den Fehler wie folgt zurück in die Schienen leiten:

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

Dann müssen Sie nur noch diese Routen in Ihrem config/routes.rb:

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

Und dann erstellen Sie einfach einen Controller, um dies zu handhaben.

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

Eine letzte Sache, die hinzugefügt werden muss: In der Entwicklungsumgebung rendert Rails normalerweise nicht die 404- oder 500-Seiten, sondern druckt stattdessen eine Rückverfolgung. Wenn Sie möchten, dass Sie ErrorsControllerim Entwicklungsmodus in Aktion sind, deaktivieren Sie das Backtrace-Material in Ihrer config/enviroments/development.rbDatei.

config.consider_all_requests_local = false
iblue
quelle
4
Vergessen Sie auch nicht, den Statuscode zu den Renderings hinzuzufügen. Andernfalls weiß Ihr Client / Browser nicht, dass es sich um einen 404/500 handelt. render: text => "404 nicht gefunden" ,: status =>: not_found
arnab
1
Ich würde sagen, dass ein reply_to-Block in den Rendering-Funktionen universeller ist: reply_to do | format | format.json {json rendern: {Fehler: "nicht gefunden"}. to_json, Status: 404} format.html {Text rendern: "404 nicht gefunden", Status: 404} Ende
Greg Funtusov
1
Ich habe meine API-Controller mit Namespaces versehen und eine Ausnahme mit einer Basisklasse wie ApiException ausgelöst, die dann im Api-Controller mit Namespace-Basis wiederhergestellt werden kann, wobei ein JSON-freundlicher Fehler mit dem entsprechenden Status wie oben zurückgegeben werden kann.
Richard Hollis
@RichardHollis Meine API-Controller haben ebenfalls einen Namespace, aber würde ich das erreichen, was Sie getan haben?
Darksky
2
Lesen Sie diese Antwort, bevor Sie auf die oben vorgeschlagene Lösung zurückgreifen: stackoverflow.com/a/29292738/2859525 . Es ist viel einfacher, 404 in einem Vorgänger-Controller abzufangen und einen Rückruf zu verwenden, um eine einfache JSON-Fehlerantwort zu erstellen.
Todd
14

Ich möchte einen separaten API-Controller erstellen, der das Format (json) und die API-spezifischen Methoden festlegt:

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-Test:

  it 'should return 404' do
    get "/api/route/specific/to/your/app/", format: :json
    expect(response.status).to eq(404)
  end
Greg Funtusov
quelle
3
Dies scheint nur für Datensätze zu funktionieren. Wie würden Sie den Fall verwalten api/non_existant_route?
Sebastialonso
1
Die Zeile rescue_from ActiveRecord::RecordNotFound, with: not_foundmuss sein, with: :not_foundaber es ist nur eine Bearbeitung mit einem Zeichen: P
fehlerhaft
11

Klar, es wird ungefähr so ​​aussehen:

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

NotFoundExceptionist nicht der wirkliche Name der Ausnahme. Dies hängt von der Rails-Version und dem gewünschten Verhalten ab. Mit einer Google-Suche ziemlich einfach zu finden.

bioneuralnet
quelle
2
Vielen Dank für die Idee, aber ich verwende Rails 3.2.2, bei dem sich die Ausnahmebehandlung geändert hat. Dies wird nicht mehr funktionieren.
iblue
@iblue Dies funktioniert in Rails 3.2+ einwandfrei und ist die bessere Lösung. Weitere Informationen finden Sie in den rescue_from Dokumenten .
Intelekshual
5

Versuchen Sie, am Ende Ihrer routes.rb:

match '*foo', :format => true, :constraints => {:format => :json}, :to => lambda {|env| [404, {}, ['{"error": "not_found"}']] }
jdoe
quelle
1
Für alle anderen, die dies lesen, müssen Sie matchin der Rails.application.routesDatei jetzt die HTTP-Methode über den via:Parameter angeben . Dies wird auch als Fehler angezeigt, wenn Sie versuchen, die obige Zeile zu verwenden.
Sameers