Skip to content

Commit

Permalink
Set a default value for Accept header in an effort to stop unexpected…
Browse files Browse the repository at this point in the history
… 406 responses
  • Loading branch information
lovett committed Nov 13, 2018
1 parent f17770d commit ea5fb14
Showing 1 changed file with 51 additions and 20 deletions.
71 changes: 51 additions & 20 deletions tools/negotiable.py
Original file line number Original file line Diff line number Diff line change
@@ -1,6 +1,9 @@
"""Decide on the content type of a response."""

import json import json
import cherrypy import cherrypy



class Tool(cherrypy.Tool): class Tool(cherrypy.Tool):
"""Decide on a suitable format for the response """Decide on a suitable format for the response
Expand All @@ -14,9 +17,9 @@ class Tool(cherrypy.Tool):
controller method.""" controller method."""


renderers = { renderers = {
"application/json": "_renderJson", "application/json": "render_json",
"text/html": "_renderHtml", "text/html": "render_html",
"text/plain": "_renderText", "text/plain": "render_text",
} }


charset = "utf-8" charset = "utf-8"
Expand All @@ -43,24 +46,31 @@ def _setup(self):
def _negotiate(self): def _negotiate(self):
"""Decide on a response format""" """Decide on a response format"""


accept = cherrypy.request.headers.get("Accept", "*/*")

# If any format is acceptable, send text/html # If any format is acceptable, send text/html
if cherrypy.request.headers.get("Accept") == "*/*": if accept == "*/*":
self.response_format = "text/html" self.response_format = "text/html"
return return


candidates = list(self.renderers.keys()) candidates = list(self.renderers.keys())
self.response_format = cherrypy.tools.accept.callable(candidates) self.response_format = cherrypy.tools.accept.callable(candidates)


def _finalize(self): def _finalize(self):
"""Transform the response body provided by the controller to its final form.""" """Transform the response body provided by the controller to its final
form.
"""


if not isinstance(cherrypy.response.body, dict): if not isinstance(cherrypy.response.body, dict):
return return


# If the body only provides one format, use it instead of # If the body only provides one format, use it instead of
# the negotiated format. # the negotiated format.
if len(cherrypy.response.body) == 1: if len(cherrypy.response.body) == 1:
renderer = "_render" + next(iter(cherrypy.response.body)).capitalize() renderer = "render_" + next(
iter(cherrypy.response.body)
).lower()
else: else:
renderer = self.renderers.get(self.response_format) renderer = self.renderers.get(self.response_format)


Expand All @@ -71,29 +81,39 @@ def _finalize(self):
cherrypy.response.body = None cherrypy.response.body = None
return return


# Requests made on the command line using curl tend to collide with the shell prompt. # Requests made on the command line using curl tend to collide
# Add some trailing newlines to prevent this. # with the shell prompt. Add some trailing newlines to
# prevent this.
body_format = "{}" body_format = "{}"


if "curl" in cherrypy.request.headers.get("User-Agent", ""): if "curl" in cherrypy.request.headers.get("User-Agent", ""):
body_format += "\n\n" body_format += "\n\n"


cherrypy.response.body = body_format.format(final_body).encode(self.charset) cherrypy.response.body = body_format.format(
final_body
).encode(self.charset)


def _renderJson(self, body): @staticmethod
def render_json(body):
"""Render a response as JSON"""
part = body.get("json") part = body.get("json")


cherrypy.response.headers["Content-Type"] = "application/json" cherrypy.response.headers["Content-Type"] = "application/json"


return json.JSONEncoder().encode(part) if part else None return json.JSONEncoder().encode(part) if part else None


def _renderManifest(self, body): @staticmethod
def render_manifest(body):
"""Render a response as an appcache manifest"""
template_file, values = body.get("manifest", (None, None)) template_file, values = body.get("manifest", (None, None))


if not template_file: if not template_file:
return None return None


template = cherrypy.engine.publish("lookup-template", template_file).pop() template = cherrypy.engine.publish(
"lookup-template",
template_file
).pop()


if not template: if not template:
return None return None
Expand All @@ -102,39 +122,50 @@ def _renderManifest(self, body):


return template.render(**values) return template.render(**values)


def _renderText(self, body): def render_text(self, body):
"""Render a response as plain text"""
part = body.get("text") part = body.get("text")


if isinstance(part, str): if isinstance(part, str):
part = [part] part = [part]


cherrypy.response.headers["Content-Type"] = "text/plain;charset={}".format(self.charset) content_type = "text/plain;charset={}".format(self.charset)
cherrypy.response.headers["Content-Type"] = content_type


return "\n".join([str(line) for line in part]) if part else None return "\n".join([str(line) for line in part]) if part else None


def _renderHtml(self, body): def render_html(self, body):
"""Render a response as HTML"""
template_file, values = body.get("html", (None, None)) template_file, values = body.get("html", (None, None))


if not template_file: if not template_file:
return None return None


template = cherrypy.engine.publish("lookup-template", template_file).pop() template = cherrypy.engine.publish(
"lookup-template",
template_file
).pop()


if not template: if not template:
return None return None


cherrypy.response.headers["Content-Type"] = "text/html;charset={}".format(self.charset) content_type = "text/html;charset={}".format(self.charset)
cherrypy.response.headers["Content-Type"] = content_type


html = template.render(**values) html = template.render(**values)


if body.get("etag_key"): if body.get("etag_key"):
hash = cherrypy.engine.publish("hasher:md5", html).pop() content_hash = cherrypy.engine.publish("hasher:md5", html).pop()


key = body.get("etag_key") key = body.get("etag_key")


cherrypy.engine.publish("memorize:etag", key, hash) cherrypy.engine.publish(
"memorize:etag",
key,
content_hash
)


cherrypy.response.headers["ETag"] = hash cherrypy.response.headers["ETag"] = content_hash


max_age = body.get("max_age") max_age = body.get("max_age")
if max_age: if max_age:
Expand Down

0 comments on commit ea5fb14

Please sign in to comment.