Within an engine w/ an API, when querying the API an exception is thrown, but the server response is not using the response specified, and instead rendering a default debug response (in production).
I can confirm that the exception is caught by the controller:
Started GET "/api_v2/admin/submissions?system_id=123&spt_id=123" for
127.0.0.1 at 2019-03-15 10:04:37 -0400
Processing by ApiV2::Admin::SubmissionsController#show as JSON
Parameters: {"system_id"=>"123", "spt_id"=>"123"}
[3, 12] in /....../emp_api_v2/app/controllers/emp_api_v2/application_controller.rb
3:
4: before_action :doorkeeper_authorize!
5:
6: rescue_from ::ActiveRecord::RecordNotFound do |e|
7: byebug
=> 8: response(:standard_error, 500, "Hello World")
9: end
10:
11: def doorkeeper_unauthorized_render_options(error: nil)
12: { json: { message: "Not authorized" }, status: 403 }
(byebug) cont
Completed 500 Internal Server Error in 5220ms (ActiveRecord: 0.0ms)
ActiveRecord::RecordNotFound - Couldn't find Emp::System with 'id'=123:
The server is expected to respond with 500 Server Error not the debug error stacktrace.
Why are there two responses, even though the controller catches the exception and runs a response method.
NOTE: This happens in dev and prod ! Server responds with 500 first (my catch response) but then with a stacktrace and 404 (As this is the source of the error and correct exception). It breaks my tests as the past response was 500. I was not able to revert my server to the old behavior by: reinstalling ruby, reinstalling rails + all gems + rolling back all changes throughout the repo. This behavior seems to be externally set by a ENV variable or something else.
I'd be grateful for any insight.
Edit: The (api) app controller looks like this:
module ApiV2
class ApplicationController < ActionController::API
before_action :doorkeeper_authorize!
rescue_from ::ActiveRecord::RecordNotFound do |e|
response(:standard_error, 500, "Hello World")
end
def doorkeeper_unauthorized_render_options(error: nil)
{ json: { message: "Not authorized" }, status: 403 }
end
end
end
Edit 2: I was able to get the correct response by wrapping the call in a rescue block. That code will result in a lot of begin/rescue blocks though as each of them has a specific error message.
begin
system = Emp::System.find(sys_id)
rescue
render json: { status: 500, content: "Specific Error msg" } and return
end
Originally i had a method as follows:
def handle_exception(message, &block)
begin
block.call
rescue Exception => e
render json: { message: message }, status: 500 and return
end
end
This will result in double render error as it's not returning from the top-level method but from the block.
Related
I have spec to test for internal server error case.
context "when network error" do
it "returns internal_server_error with network error description" do
allow(Authorer::AuthorizationClient).to receive(:post).and_raise(EOFError)
if request_method == "get"
get url, params: params, headers: #header
else
#header["Content-Type"] = "application/vnd.api+json"
post url, params: params, as: :json, headers: #header
end
reload_response_body
expect(response).to have_http_status(:internal_server_error)
end
end
CONTEXT: Authorer::AuthorizationClient is our client code to access some third-party server, and sometimes we face EOFError then we need to handle it by rescuing it and raise again to client for internal server error.
something like this
# code
....
begin
Authorer::AuthorizationClient.evaluate_permission(id) # this one hits third-party
rescue EOFError
raise StandardError, "Network Error"
end
the test was success and green. but there is error log and traces
[test] [2022-08-15 11:22:52.397] [id.test] [E] {"message":null,"exception":"Network Error","extra":null,"context":null}
/app/service/authorer/authorization_client.rb:29:in `rescue in authorize_access'
/app/service/authorer/authorization_client.rb:21:in `rescue in authorize_user'
........
How to omit this error log?
thanks in advance
I have a Rails 6 app to which users can upload CSV files. Rails/Rack imposes a limit in the number of params that can be included in a request, and I've set this to a size larger than likely submissions to my app. However, I would like to return a friendly response if a too-large file is uploaded.
It looks like I need to add some custom middleware, to catch and rescue the error, but I can't get the code to work - the basic error is still raised without my rescue block being called.
The error from the server is:
Rack app error handling request { POST /[PATH_TO]/datasets }
#<RangeError: exceeded available parameter key space>
The code in my app/middleware/catch_errors.rb file is basically taken from a previous SO answer, where someone was catching ActionDispatch::ParamsParser::ParseError in JSON, but with my own code in the rescue block (which I realise may not work properly in this context, but that's not the issue right now):
class CatchErrors
def initialize(_app)
#app = _app
end
def call(_env)
begin
#app.call(_env)
rescue RangeError => _error
_error_output = "There were too many fields in the data you submitted: #{_error}"
if env['HTTP_ACCEPT'] =~ /application\/html/
Rails.logger.error("Caught RangeError: #{_error}")
flash[:error_title] = 'Too many fields in your data'
flash[:error_detail1] = _error_output
render 'static_pages/error', status: :bad_request
elsif env['HTTP_ACCEPT'] =~ /application\/json/
return [
:bad_request, { "Content-Type" => "application/json" },
[ { status: :bad_request, error: _error_output }.to_json ]
]
else
raise _error
end
end
end
end
I'm loading it in config.application.rb like this:
require_relative '../app/middleware/catch_errors'
...
config.middleware.use CatchErrors
I'm resetting the size limit for testing in app/initializers/rack.rb like this:
if Rack::Utils.respond_to?("key_space_limit=")
Rack::Utils.key_space_limit = 1
end
Any help gratefully received!
First, execute command to see all middlewares:
bin/rails middleware
config.middleware.use place your middleware at the bottom of the middleware stack. Because of that it can not catch error. Try to place it at the top:
config.middleware.insert_before 0, CatchErrors
Another point to mention, may be you will need to config.middleware.move_after or even config.middleware.delete some middleware. For instance, while tinkering I needed to place:
config.middleware.move_after CatchErrors, Rack::MiniProfiler
I am using a Rails gem to send requests to an api using RestClient. I need to rescue a 401 error code. I saw the following on RestClient documentation:
> RestClient.get('http://my-rest-service.com/resource'){ |response,
> request, result, &block| case response.code when 200
> p "It worked !"
> response when 423
> raise SomeCustomExceptionIfYouWant else
> response.return!(request, result, &block) end }
I have attempted to implement a similar case statement:
case response.code
when 200
JSON.parse(response.body)
when 401
raise AuthenicationError, "Unauthorized"
else
raise RestClient::ExceptionWithResponse
end
It captures the 200 case fine but ignores the 401 case and goes straight to the else. Any suggestions on raising an exception for 401 on a response that's coming back through RestClient?
I can't tell you and why I'm sure the rest-client repo can tell you :) ... but using RestClient::Request.new then executing the api call with a block works for me.
I think it probably has to do with the fact that the RestClient has built in exceptions.
request = RestClient::Request.new(
method: :get,
url: 'https://my-rest-service.com/resource.json')
response = request.execute {|response| response}
case response.code
when 200
puts "Good"
when 401
puts "Bad"
raise Exception
end
It captures the 200 case fine but ignores the 401 case and goes straight to the else.
I rather suspect it does not go to the else, actually; you'd still get a RestClient::ExceptionWithResponse raised even if you took out the else clause completely, because that's what RestClient.get does when it gets an error response such as in the 400 or 500 range. From the README:
for result codes between 200 and 207, a RestClient::Response will be returned
for result codes 301, 302 or 307, the redirection will be followed if the request is a GET or a HEAD
for result code 303, the redirection will be followed and the request transformed into a GET
for other cases, a RestClient::ExceptionWithResponse holding the Response will be raised; a specific exception class will be thrown for known error codes
call .response on the exception to get the server's response
You are using wrong HTTP code. Unauthorized is actually 401, not 410.
If you catch the exception from Request.execute in a rescue block, note that you can also get the response body form the exception, example:
def request(method, url, params = {})
resp = RestClient::Request.execute(
method: method,
url: url,
timeout: 30,
accept: :json,
payload: params.to_json,
headers: {
content_type: :json,
}
)
JSON.parse(resp.body)
rescue => e
{ error: e.message, body: JSON.parse(e.response.body) } # <-------------
end
In a rails app I am running:
54 def itunes_all_apps
55 begin
56 Spaceship::Tunes.login(params[:itunes_username], params[:itunes_password])
57 apps = Spaceship::Tunes::Application.all
58 render json: apps.to_json, status: 200
59 rescue => e
60 render json: {error: e}.to_json, status: 500
61 end
62 end
It returns a status 500 error with no other information every time.
However, if I change this around slightly, for example getting teams (note, from Spaceship, not Spaceship::Tunes) this works fine:
def itunes_all_apps
begin
spaceship = Spaceship.login(params[:itunes_username], params[:itunes_password])
teams = spaceship.teams
render json: teams.to_json, status: 200
rescue => e
render json: {error: e}.to_json, status: 500
end
end
I'm not using any fast file or or config or anything. Just passing in a username and password via an api call and trying to get a response back. I'm new to rails so it may be my implementation of the Spaceship examples provided.
Using spaceship 0.36.1 gem (the latest)
I've pored through the docs to no avail. Grasping for any leads on what I'm doing wrong.
http://www.rubydoc.info/gems/spaceship/Spaceship/Tunes
https://github.com/fastlane/fastlane/blob/master/spaceship/docs/iTunesConnect.md
Someone suggested I run these two commands in irb, which I did, and they worked perfect!
Spaceship::Tunes.login('myAppleId', 'myPassword')
Spaceship::Tunes::Application.all
So it's not an iTunes account problem or credentials problem (because it works in irb), routes problem (because I ran both rails methods above with same route), or params problem (because I ran both rails methods above with same param names).
I really appreciate any suggestions. Thanks.
Edit:
Commenting out begin, rescue, and rending the error, the stack trace is as follows:
2016-10-24T17:47:34.974650+00:00 app[web.1]: Started POST "/api/v1/users/13/itunes_all_apps" for 162.237.102.13 at 2016-10-24 17:47:34 +0000
2016-10-24T17:47:34.977478+00:00 app[web.1]: Processing by Api::V1::UsersController#itunes_all_apps as JSON
2016-10-24T17:47:34.977521+00:00 app[web.1]: Parameters: {"itunes_username"=>"myCorrectUsername", "itunes_password"=>"[FILTERED]", "team_id"=>"myCorrectTeamId", "id"=>"13", "user"=>{}}
2016-10-24T17:47:35.629629+00:00 heroku[router]: at=info method=POST path="/api/v1/users/13/itunes_all_apps" host=myHerokuApp.herokuapp.com request_id=002d906d-354e-4633-8b54-71aa5181e3a7 fwd="161.237.102.13" dyno=web.1 connect=2ms service=657ms status=500 bytes=259
2016-10-24T17:47:35.619597+00:00 app[web.1]: Completed 500 Internal Server Error in 642ms (ActiveRecord: 0.0ms)
2016-10-24T17:47:35.620430+00:00 app[web.1]:
2016-10-24T17:47:35.620432+00:00 app[web.1]: IOError (not opened for reading):
2016-10-24T17:47:35.620434+00:00 app[web.1]:
2016-10-24T17:47:35.620433+00:00 app[web.1]: app/controllers/api/v1/users_controller.rb:58:in `itunes_all_apps'
It seems that Spaceship::Fastlane::Application does not implement as_json method and the default as_json touches some IO object, which cannot be represented as json.
My suggestion would be to create JSON serializer. You could use active_model-serializer, but if you do not want to create a dependency just for one object, then you can create your own serializer.
class SpaceshipApplicationSerializer
attr_reader :spaceship_applications
def initialize(spaceship_applications)
#spaceship_applications = spaceship_applications
end
def as_json(options = {})
spaceship_applications.each_with_object([]) do |spaceship_application, memo|
memo << object_as_json(spaceship_application)
end
end
def object_as_json(object)
attributes.each_with_object({}) do |attribute, memo|
memo[attribute] = object.send(attribute)
end
end
def attributes
[
:apple_id,
:name,
:vendor_id,
:bundle_id,
:last_modified,
:issues_count,
:app_icon_preview_url
]
end
end
# In your controller
def itunes_all_apps
begin
Spaceship::Tunes.login(params[:itunes_username], params[:itunes_password])
apps = Spaceship::Tunes::Application.all
render json: SpaceshipApplicationSerializer.new(apps).to_json, status: 200
rescue => e
render json: {error: e}.to_json, status: 500
end
end
EDIT:
Yes, the classes return an array, but the actual objects in array don't play nicely with json. It's hard to say if the problem is with the library - on one hand Spaceship::Tunes::Application not returning a proper json representation is a missing feature, but if the to_json raises an exception (a method the class responds to) - then I would say that is a bug.
Creating your own serializer to build json representation the way you want it - is a common pattern.
I'm doing an HTTP request from a Rails action like so:
class TeamController < ApplicationController
def test
_uri = 'http://v.youku.com/player/getPlayList/VideoIDS/XNjQyNjg3ODg0_ev_5'
_html_response = nil
open(_uri) do |http|
_html_response = http.read
end
render text: _html_response
end
end
But I get the error:
wrong status line: "TTP/1.1 302 Found"
But I used the same code in a simple Ruby file and I got the response without any errors. What am I doing wrong?
thanks,I find the reason,that's because my ubuntu vm is forbided to visit this site.sheet!