Changing Http Status Message Using Sinatra

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



Leave a reply



Submit