Changing HTTP status message using Sinatra
The status message is generated by the server you are using, e.g. in Thin the messages are in Thin::HTTP_STATUS_CODES
and the reponse line is generated in Thin::Response
, and in WEBrick they are in WEBrick::HHTPStatus::StatusMessage
and the response is generated in WEBrick::HTTPResponse
.
If you know what server you are using, you could add your error to the appropriate hash.
With Thin:
require 'thin'
Thin::HTTP_STATUS_CODES[453] = "Client Error"
and the output:
$ curl -v localhost:4567
* About to connect() to localhost port 4567 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 4567 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8r zlib/1.2.3
> Host: localhost:4567
> Accept: */*
>
< HTTP/1.1 453 Client Error
< X-Frame-Options: sameorigin
< X-XSS-Protection: 1; mode=block
< Content-Type: text/html;charset=utf-8
< Content-Length: 0
< Connection: keep-alive
< Server: thin 1.4.1 codename Chromeo
<
* Connection #0 to host localhost left intact
* Closing connection #0
and with WEBrick:
require 'webrick'
WEBrick::HTTPStatus::StatusMessage[453] = "Client Error"
which gives the output:
$ curl -v localhost:4567
* About to connect() to localhost port 4567 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 4567 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8r zlib/1.2.3
> Host: localhost:4567
> Accept: */*
>
localhost - - [13/Aug/2012:01:41:48 BST] "GET / HTTP/1.1" 453 0
- -> /
< HTTP/1.1 453 Client Error
< X-Frame-Options: sameorigin
< X-Xss-Protection: 1; mode=block
< Content-Type: text/html;charset=utf-8
< Content-Length: 0
< Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20)
< Date: Mon, 13 Aug 2012 00:41:48 GMT
< Connection: Keep-Alive
<
* Connection #0 to host localhost left intact
* Closing connection #0
How do I output error messages with HTTP error codes in Sinatra?
Look in the body
variable, you'll have the error message in the first index, so in body[0]
.
Setting status code in Sinatra's custom error block
Doh. All I need is to set status 400
:
error ParamXMissingError do
status 400
haml :custom_error_page
end
How to raise a custom error code in sinatra?
Something like raise 404
raises an error just like raise ZeroDivisionError
would, which causes your app to throw a 500 Internal Server Error. The simplest way to return a specific error is to use status
get '/raise404' do
status 404
end
You can also add a custom response body with body
get '/raise403' do
status 403
body 'This is a 403 error'
end
How should I return Sinatra HTTP errors from inside a class where HALT is not available?
This question is probably better for CodeReview but one approach you can see in an OO design here is a 'halt' path and a 'happy' path. Your class just needs to implement a few methods to help this be consistent across all your sinatra routes and methods.
Here's one approach, and it would be easy to adopt this kind of interface across other classes using inheritance.
post '/api/process_something' do
offer_manager = OfferManager.new(params)
# error guard clause
halt offer_manager.status, offer_manager.halt_message if offer_manager.halt?
# validations met, continue to process
offer_manager.process_offer
# return back 200
offer_manager.status
end
class OfferManager
attr_reader :status, :params, :halt_message
def initialize(params)
@params = params
validate_params
end
def process_offer
do_some_processing
end
def halt?
# right now we just know missing params is one error to halt on but this is where
# you could implement more business logic if need be
missing_params?
end
private
def validate_params
if missing_params?
@status = 404
@halt_message = "missing #{missing_keys.join(", ")} key(s)"
else
@status = 200
end
end
def do_some_processing
# go do other processing
end
def missing_params?
missing_keys.size > 0
end
def missing_keys
expected_keys = [:profile_id, :offer_id]
params.select { |k, _| !expected_keys.has_key?(k) }
end
end
Displaying Error Message with Sinatra
You can use the 'sinatra-flash' gem to display all kinds of errors/notices etc.
u = User.new
u.email = params[:email]
u.save
if u.save
redirect '/'
else
flash[:error] = "Format of the email was wrong."
redirect '/'
end
Then you need to say where you want the flash[:error] to be displayed. Normally I put this in the layout.haml or (erb) file right above where I yield in the content.
layout.haml:
- if flash[:error]
%p
= flash[:error]
Also, make sure you include the gem and enable sessions
require 'sinatra'
require 'sinatra/flash'
enable :sessions
You could also try the 'rack-flash' gem. There is a tutorial for using it at http://ididitmyway.heroku.com/past/2011/3/15/rack_flash_/
Sinatra not sending headers
It was not CORS that was the problem. But my crappy jquery implementation. So make sure you don't copy it!
In Ruby/Sinatra, how to halt with an ERB template and error message
Why doesn't it work − use the source!
Lets look at the Sinatra source code to see why this problem doesn't work. The main Sinatra file (lib/sinatra/base.rb
) is just 2043 lines long, and pretty readable code!
All halt
does is:
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
And exceptions are caught with:
# Dispatch a request with error handling.
def dispatch!
invoke do
static! if settings.static? && (request.get? || request.head?)
filter! :before
route!
end
rescue ::Exception => boom
invoke { handle_exception!(boom) }
[..]
end
def handle_exception!(boom)
@env['sinatra.error'] = boom
[..]
end
But for some reason this code is never run (as tested with basic "printf-debugging"). This is because in invoke
the block is run like:
# Run the block with 'throw :halt' support and apply result to the response.
def invoke
res = catch(:halt) { yield }
res = [res] if Fixnum === res or String === res
if Array === res and Fixnum === res.first
res = res.dup
status(res.shift)
body(res.pop)
headers(*res)
elsif res.respond_to? :each
body res
end
nil # avoid double setting the same response tuple twice
end
Notice the catch(:halt)
here. The if Array === res and Fixnum === res.first
part is what halt
sets and how the response body and status code are set.
The error 403 { .. }
block is run in call!
:
invoke { error_block!(response.status) } unless @env['sinatra.error']
So now we understand why this doesn't work, we can look for solutions ;-)
So can I use halt some way?
Not as far as I can see. If you look at the body of the invoke
method, you'll see that the body is always set when using halt
. You don't want this, since you want to override the response body.
Solution
Use a "real" exception and not the halt
"pseudo-exception". Sinatra doesn't seem to come with pre-defined exceptions, but the handle_exception!
does look at http_status
to set the correct HTTP status:
if boom.respond_to? :http_status
status(boom.http_status)
elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
status(boom.code)
else
status(500)
end
So you could use something like this:
require 'sinatra'
class PermissionDenied < StandardError
def http_status; 403 end
end
get '/error' do
#halt 403, 'My special message to you!'
raise PermissionDenied, 'My special message to you!'
end
error 403 do
'Error message -> ' + @env['sinatra.error'].message
end
Which works as expected (the output is Error message -> My special message to you!
). You can return an ERB template here.
Related Topics
Ruby Is Already Using the Class Name of My Model
Rails Cancan and State MAChine - Authorizing States
Insert Rows on Specific Line in a File
Project Euler #3 in Ruby Solution Times Out
Compass Error for Command 'Grunt Server'
Scraping an Angularjs Application
Linking Two Models in a Multi-Model Form
How to Refactor Openssl Pkcs5_Keyivgen in Ruby
Error: "Fatal: I Don't Handle Protocol ''Git' When Using Bundle Install
Mongodb Group Using Ruby Driver
How to Specify a Struct as the Return Value of a Function in Rubyffi
Google Analytics API Error "Selected Dimensions and Metrics Cannot Be Queried Together."