Repository: plone/Products.CMFPlone

Issue:
Return HTTP errors in proper format
Proposer: Eric Brehault
Seconder:
# Motivation

When a page does not exist, or has an error, or is not allowed for the user, Plone returns the appropriate HTTP error (404, 500, ...), and the response is a human readable page, properly skinned, which nice for the user.
And if the requested resource is not a page (an image, a JS file, an AJAX call, etc.), Plone also returns this human readable page.
It is useless because the page will not be displayed, and it produces many problems:
- the response is very heavy,
- it involves a lot of processing (Plone will render an entire page for nothing),
- for AJAX call, the response cannot be easily interperted,
- it might produce a cascade of errors (for instance: the regular response is not supposed to be rendered via Diazo, as it is not an HTML page, but the error is rendered by Diazo, and it might produce another error).
# Proposed solution

We could display the human readable error page only if the current request `HTTP_ACCEPT` parameter contains `text/html`, in other cases, we would just return a simple JSON error reponse.
# Proposed implementation

Test the `HTTP_ACCEPT` value in `Products/CMFPlone/skins/plone_templates/standard_error_message.py`, and call the existing template or make a JSON response accordingly.
# Risks

No identified risks.



Discussion:
+1

+1

+1!

+1

Though, we should keep in mind that we will need proper content negotiation and JSON error responses for plone.restapi as well. Therefore it might make sense to implement that in plone.restapi and then either backport the code or even ship with a minimal plone.restapi version in Plone 5.1.

@ebrehault I stated to play around a bit and implemented a JS 404 for plone.restapi:

https://github.com/plone/plone.restapi/pull/24/files

The error response format is still work in progress and so far this is just a proof-of-concept. Though, I think we need to override/monkey patch quite a few other things in order to make 500, 401 and other errors work. Maybe you have time for a quick chat (IRC, hangout) during the sprint to discuss things...

late +1 
but is there any implementation around?

+1

FWIW, we normally implementing a workaround for this using nginx:

```
proxy_cache_path /var/lib/nginx/cache levels=1:2 keys_zone=backcache:8m max_size=50m;
proxy_cache_key "$request_uri$is_args$args";
proxy_cache_min_uses 2;
proxy_cache_valid 404 1d;
```

@jensens nothing has been done yet, but I think I could do a very simple fix (as explained in the issue) and we will replace it with plone.restapi later when it is ready as @tisto suggested.


Resolving patch:
diff --git a/Products/CMFPlone/skins/plone_templates/standard_error_message.py b/Products/CMFPlone/skins/plone_templates/standard_error_message.py
--- a/Products/CMFPlone/skins/plone_templates/standard_error_message.py
+++ b/Products/CMFPlone/skins/plone_templates/standard_error_message.py
@@ -27,6 +27,10 @@
 error_traceback = kwargs.get('error_traceback', None)
 error_value = kwargs.get('error_value', None)
 
+if "text/html" not in context.REQUEST.getHeader('Accept', ''):
+    context.REQUEST.RESPONSE.setHeader("Content-Type", "application/json")
+    return '{"error_type": "{0:s}"}'.format(error_type)
+
 if error_log_url:
     error_log_id = error_log_url.split('?id=')[1]
 else:


Test patch:
diff --git a/Products/CMFPlone/tests/redirection.txt b/Products/CMFPlone/tests/redirection.txt
--- a/Products/CMFPlone/tests/redirection.txt
+++ b/Products/CMFPlone/tests/redirection.txt
@@ -31,5 +31,16 @@ But it should not give an error while rendering the default error page:
 
   >>> "the following error occurred while attempting to render the standard error message" in browser.contents
   False
+
+As it is an image, it should return a JSON message
+  >>> '{"error_type": "NotFound"}' in browser.contents
+  True
+
+A non-existing page would return a human readable error page
+  >>> browser.addHeader('Accept', 'text/html')
+  >>> browser.open('http://nohost/plone/non-existing-page')
+  Traceback (most recent call last):
+  ...
+  HTTPError: HTTP Error 404: Not Found
   >>> "This page does not seem to exist" in browser.contents
   True