Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Html exceptions only when accepted #592

Closed
wants to merge 4 commits into from

3 participants

@bestie

Rather than be concerned with whether a request is an asynchronous browser
request or not it is better to simply consider the Accept header and only serve
HTML to clients that specifically ask for it.

This way you will not find your pure JSON API application splitting out HTML
error messages to your console when using curl :)

bestie added some commits
@bestie bestie ShowException only serves HTML Accept header contains text/html
Rather than be concerned with whether a request is an asynchronous browser
request or not it is better to simply consider the Accept header and only serve
HTML to clients that specifically ask for it.

This way you will not find your pure JSON API application splitting out HTML
error messages to your console when using curl :)
58490f5
@bestie bestie ShowExceptions minor refactoring
* Load HTML exception template only if needed
* Only #call is public
* Enumerable body concern in one place
6eb5f29
lib/rack/showexceptions.rb
((28 lines not shown))
end
- def prefers_plain_text?(env)
- env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" && (!env["HTTP_ACCEPT"] || !env["HTTP_ACCEPT"].include?("text/html"))
+ private
@gioele
gioele added a note

Isn't marking all the following methods as private a too invasive change?

@bestie
bestie added a note

I think it would be very odd if anyone was making use of these methods so I thought better to close up the public API to make later changes easier.

I would think it very odd if someone was making use of these methods, though I do appreciate your concern.

@gioele
gioele added a note

I see you point and agree with you, but I think that this kind of changes are not OK unless there is a major version bump to notify of possible breakage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@bestie

I've restored the public API to it's previous state, also I've made sure the new method I've added is private.

Hope this is now acceptable. I'm happy to squash commits if that's your preference.

@bestie

@spastorino @raggi Any thoughts on this?

I'm having to monkey patch this change in on all my JSON API apps otherwise every time you get an error it's a huge PITA :)

lib/rack/showexceptions.rb
@@ -17,7 +17,6 @@ class ShowExceptions
def initialize(app)
@app = app
- @template = ERB.new(TEMPLATE)
@raggi Owner
raggi added a note

ERB parsing is not cheap. Why was this moved, it seems orthogonal to the desired changes?

@bestie
bestie added a note

Just a refactoring I thought might be beneficial.

Now no ERB object is created unless HTML is about to rendered. This should improve performance if anything.

Happy to revert if this is a deal breaker.

@raggi Owner
raggi added a note

You want to improve boot time performance at the sacrifice of runtime performance? I'd be more worried about the extra garbage, though.

@bestie
bestie added a note

Yes that's right, as the template only needs to rendered in the case of an exception isn't it better to take the performance hit only when one is raised rather than on every request?

@raggi Owner
raggi added a note

Initialize is not called every request.

@bestie
bestie added a note

Of course you're right. However this change still prevents all template related objects from being created until absolutely necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@raggi
Owner

Looks good in principle. Would you mind addressing the comment?

@bestie bestie Undo template refactoring
As this is orthoganol to HTML rendering change.
5b2ee11
@bestie

While I stand by my refactorings I agree that the discussed changes to the template rendering are orthogonal to the purpose of this PR.

I have therefore removed it.

@raggi raggi commented on the diff
lib/rack/showexceptions.rb
@@ -85,7 +94,7 @@ def pretty(env, exception)
end
}.compact
- [@template.result(binding)]
+ @template.result(binding)
@raggi Owner
raggi added a note

Why did you remove the array here? Doesn't ERB return strings from result?

Rack SPEC says "Body must respond to #each and yield strings". String does not do this in all supported ruby versions in all locales.

@bestie
bestie added a note

I've moved the concern of wrapping the response body string in an array to one place. See line 45 https://github.com/bestie/rack/blob/5b2ee1163aa6f68df5ec712b4d9802d90a7875f4/lib/rack/showexceptions.rb#L45

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@raggi raggi commented on the diff
lib/rack/showexceptions.rb
((32 lines not shown))
end
- def prefers_plain_text?(env)
- env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" && (!env["HTTP_ACCEPT"] || !env["HTTP_ACCEPT"].include?("text/html"))
+ def accepts_html?(env)
+ env["HTTP_ACCEPT"] && env["HTTP_ACCEPT"].include?("text/html")
@raggi Owner
raggi added a note

We have Rack::Utils.best_q_match and friends now, that are probably better used here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@raggi raggi commented on the diff
test/spec_showexceptions.rb
@@ -47,7 +47,7 @@ def show_exceptions(app)
res.body.should.include __FILE__
end
- it "responds with HTML on AJAX requests accepting HTML" do
+ it "responds with HTML to requests specifically accepting HTML" do
@raggi Owner
raggi added a note

Is this test description correct? We should give back HTML for /, and if/once we do, then this description is slightly incorrect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@raggi raggi added this to the Rack 1.5.3 milestone
@raggi raggi closed this in 6f1a4a4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 10, 2013
  1. @bestie

    ShowException only serves HTML Accept header contains text/html

    bestie authored
    Rather than be concerned with whether a request is an asynchronous browser
    request or not it is better to simply consider the Accept header and only serve
    HTML to clients that specifically ask for it.
    
    This way you will not find your pure JSON API application splitting out HTML
    error messages to your console when using curl :)
  2. @bestie

    ShowExceptions minor refactoring

    bestie authored
    * Load HTML exception template only if needed
    * Only #call is public
    * Enumerable body concern in one place
Commits on Aug 13, 2013
  1. @bestie

    Restore public API

    bestie authored
Commits on Jan 10, 2014
  1. @bestie

    Undo template refactoring

    bestie authored
    As this is orthoganol to HTML rendering change.
This page is out of date. Refresh to see the latest.
Showing with 26 additions and 17 deletions.
  1. +20 −11 lib/rack/showexceptions.rb
  2. +6 −6 test/spec_showexceptions.rb
View
31 lib/rack/showexceptions.rb
@@ -28,23 +28,32 @@ def call(env)
env["rack.errors"].puts(exception_string)
env["rack.errors"].flush
- if prefers_plain_text?(env)
- content_type = "text/plain"
- body = [exception_string]
- else
+ if accepts_html?(env)
content_type = "text/html"
body = pretty(env, e)
+ else
+ content_type = "text/plain"
+ body = exception_string
end
- [500,
- {"Content-Type" => content_type,
- "Content-Length" => Rack::Utils.bytesize(body.join).to_s},
- body]
+ [
+ 500,
+ {
+ "Content-Type" => content_type,
+ "Content-Length" => Rack::Utils.bytesize(body).to_s,
+ },
+ [body],
+ ]
+ end
+
+ def prefers_plaintext?(env)
+ !accepts_html(env)
end
- def prefers_plain_text?(env)
- env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" && (!env["HTTP_ACCEPT"] || !env["HTTP_ACCEPT"].include?("text/html"))
+ def accepts_html?(env)
+ env["HTTP_ACCEPT"] && env["HTTP_ACCEPT"].include?("text/html")
@raggi Owner
raggi added a note

We have Rack::Utils.best_q_match and friends now, that are probably better used here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
end
+ private :accepts_html?
def dump_exception(exception)
string = "#{exception.class}: #{exception.message}\n"
@@ -85,7 +94,7 @@ def pretty(env, exception)
end
}.compact
- [@template.result(binding)]
+ @template.result(binding)
@raggi Owner
raggi added a note

Why did you remove the array here? Doesn't ERB return strings from result?

Rack SPEC says "Body must respond to #each and yield strings". String does not do this in all supported ruby versions in all locales.

@bestie
bestie added a note

I've moved the concern of wrapping the response body string in an array to one place. See line 45 https://github.com/bestie/rack/blob/5b2ee1163aa6f68df5ec712b4d9802d90a7875f4/lib/rack/showexceptions.rb#L45

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
end
def h(obj) # :nodoc:
View
12 test/spec_showexceptions.rb
@@ -16,7 +16,7 @@ def show_exceptions(app)
))
lambda{
- res = req.get("/")
+ res = req.get("/", "HTTP_ACCEPT" => "text/html")
}.should.not.raise
res.should.be.a.server_error
@@ -26,7 +26,7 @@ def show_exceptions(app)
res.should =~ /ShowExceptions/
end
- it "responds with plain text on AJAX requests accepting anything but HTML" do
+ it "responds with plain text to requests not specifically accepting HTML" do
res = nil
req = Rack::MockRequest.new(
@@ -35,7 +35,7 @@ def show_exceptions(app)
))
lambda{
- res = req.get("/", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")
+ res = req.get( "/", "HTTP_ACCECPT" => "*/*")
}.should.not.raise
res.should.be.a.server_error
@@ -47,7 +47,7 @@ def show_exceptions(app)
res.body.should.include __FILE__
end
- it "responds with HTML on AJAX requests accepting HTML" do
+ it "responds with HTML to requests specifically accepting HTML" do
@raggi Owner
raggi added a note

Is this test description correct? We should give back HTML for /, and if/once we do, then this description is slightly incorrect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
res = nil
req = Rack::MockRequest.new(
@@ -56,7 +56,7 @@ def show_exceptions(app)
))
lambda{
- res = req.get("/", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", "HTTP_ACCEPT" => "text/html")
+ res = req.get("/", "HTTP_ACCEPT" => "text/html")
}.should.not.raise
res.should.be.a.server_error
@@ -79,7 +79,7 @@ def show_exceptions(app)
)
lambda{
- res = req.get("/")
+ res = req.get("/", "HTTP_ACCEPT" => "text/html")
}.should.not.raise
res.should.be.a.server_error
Something went wrong with that request. Please try again.