1# Copyright 2013 by Rackspace Hosting, Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""HTTPError exception class.""" 16 17from collections import OrderedDict 18import xml.etree.ElementTree as et 19 20from falcon.util import json, uri 21 22 23class HTTPError(Exception): 24 """Represents a generic HTTP error. 25 26 Raise an instance or subclass of ``HTTPError`` to have Falcon return 27 a formatted error response and an appropriate HTTP status code 28 to the client when something goes wrong. JSON and XML media types 29 are supported by default. 30 31 To customize the error presentation, implement a custom error 32 serializer and set it on the :class:`~.API` instance via 33 :meth:`~.API.set_error_serializer`. 34 35 To customize what data is passed to the serializer, subclass 36 ``HTTPError`` and override the ``to_dict()`` method (``to_json()`` 37 is implemented via ``to_dict()``). To also support XML, override 38 the ``to_xml()`` method. 39 40 Attributes: 41 status (str): HTTP status line, e.g. '748 Confounded by Ponies'. 42 has_representation (bool): Read-only property that determines 43 whether error details will be serialized when composing 44 the HTTP response. In ``HTTPError`` this property always 45 returns ``True``, but child classes may override it 46 in order to return ``False`` when an empty HTTP body is desired. 47 48 (See also: :class:`falcon.http_error.NoRepresentation`) 49 50 Note: 51 A custom error serializer 52 (see :meth:`~.API.set_error_serializer`) may choose to set a 53 response body regardless of the value of this property. 54 55 title (str): Error title to send to the client. 56 description (str): Description of the error to send to the client. 57 headers (dict): Extra headers to add to the response. 58 link (str): An href that the client can provide to the user for 59 getting help. 60 code (int): An internal application code that a user can reference when 61 requesting support for the error. 62 63 Args: 64 status (str): HTTP status code and text, such as "400 Bad Request" 65 66 Keyword Args: 67 title (str): Human-friendly error title. If not provided, defaults 68 to the HTTP status line as determined by the ``status`` argument. 69 description (str): Human-friendly description of the error, along with 70 a helpful suggestion or two (default ``None``). 71 headers (dict or list): A ``dict`` of header names and values 72 to set, or a ``list`` of (*name*, *value*) tuples. Both *name* and 73 *value* must be of type ``str`` or ``StringType``, and only 74 character values 0x00 through 0xFF may be used on platforms that 75 use wide characters. 76 77 Note: 78 The Content-Type header, if present, will be overridden. If 79 you wish to return custom error messages, you can create 80 your own HTTP error class, and install an error handler 81 to convert it into an appropriate HTTP response for the 82 client 83 84 Note: 85 Falcon can process a list of ``tuple`` slightly faster 86 than a ``dict``. 87 88 href (str): A URL someone can visit to find out more information 89 (default ``None``). Unicode characters are percent-encoded. 90 href_text (str): If href is given, use this as the friendly 91 title/description for the link (default 'API documentation 92 for this error'). 93 code (int): An internal code that customers can reference in their 94 support request or to help them when searching for knowledge 95 base articles related to this error (default ``None``). 96 """ 97 98 __slots__ = ( 99 'status', 100 'title', 101 'description', 102 'headers', 103 'link', 104 'code', 105 ) 106 107 def __init__(self, status, title=None, description=None, headers=None, 108 href=None, href_text=None, code=None): 109 self.status = status 110 111 # TODO(kgriffs): HTTP/2 does away with the "reason phrase". Eventually 112 # we'll probably switch over to making everything code-based to more 113 # easily support HTTP/2. When that happens, should we continue to 114 # include the reason phrase in the title? 115 self.title = title or status 116 117 self.description = description 118 self.headers = headers 119 self.code = code 120 121 if href: 122 link = self.link = OrderedDict() 123 link['text'] = (href_text or 'Documentation related to this error') 124 link['href'] = uri.encode(href) 125 link['rel'] = 'help' 126 else: 127 self.link = None 128 129 def __repr__(self): 130 return '<%s: %s>' % (self.__class__.__name__, self.status) 131 132 @property 133 def has_representation(self): 134 return True 135 136 def to_dict(self, obj_type=dict): 137 """Return a basic dictionary representing the error. 138 139 This method can be useful when serializing the error to hash-like 140 media types, such as YAML, JSON, and MessagePack. 141 142 Args: 143 obj_type: A dict-like type that will be used to store the 144 error information (default ``dict``). 145 146 Returns: 147 dict: A dictionary populated with the error's title, 148 description, etc. 149 150 """ 151 152 obj = obj_type() 153 154 obj['title'] = self.title 155 156 if self.description is not None: 157 obj['description'] = self.description 158 159 if self.code is not None: 160 obj['code'] = self.code 161 162 if self.link is not None: 163 obj['link'] = self.link 164 165 return obj 166 167 def to_json(self): 168 """Return a pretty-printed JSON representation of the error. 169 170 Returns: 171 str: A JSON document for the error. 172 173 """ 174 175 obj = self.to_dict(OrderedDict) 176 return json.dumps(obj, ensure_ascii=False) 177 178 def to_xml(self): 179 """Return an XML-encoded representation of the error. 180 181 Returns: 182 str: An XML document for the error. 183 184 """ 185 186 error_element = et.Element('error') 187 188 et.SubElement(error_element, 'title').text = self.title 189 190 if self.description is not None: 191 et.SubElement(error_element, 'description').text = self.description 192 193 if self.code is not None: 194 et.SubElement(error_element, 'code').text = str(self.code) 195 196 if self.link is not None: 197 link_element = et.SubElement(error_element, 'link') 198 199 for key in ('text', 'href', 'rel'): 200 et.SubElement(link_element, key).text = self.link[key] 201 202 return (b'<?xml version="1.0" encoding="UTF-8"?>' + 203 et.tostring(error_element, encoding='utf-8')) 204 205 206class NoRepresentation(object): 207 """Mixin for ``HTTPError`` child classes that have no representation. 208 209 This class can be mixed in when inheriting from ``HTTPError``, in order 210 to override the `has_representation` property such that it always 211 returns ``False``. This, in turn, will cause Falcon to return an empty 212 response body to the client. 213 214 You can use this mixin when defining errors that either should not have 215 a body (as dictated by HTTP standards or common practice), or in the 216 case that a detailed error response may leak information to an attacker. 217 218 Note: 219 This mixin class must appear before ``HTTPError`` in the base class 220 list when defining the child; otherwise, it will not override the 221 `has_representation` property as expected. 222 223 """ 224 225 @property 226 def has_representation(self): 227 return False 228 229 230class OptionalRepresentation(object): 231 """Mixin for ``HTTPError`` child classes that may have a representation. 232 233 This class can be mixed in when inheriting from ``HTTPError`` in order 234 to override the `has_representation` property, such that it will 235 return ``False`` when the error instance has no description 236 (i.e., the `description` kwarg was not set). 237 238 You can use this mixin when defining errors that do not include 239 a body in the HTTP response by default, serializing details only when 240 the web developer provides a description of the error. 241 242 Note: 243 This mixin class must appear before ``HTTPError`` in the base class 244 list when defining the child; otherwise, it will not override the 245 `has_representation` property as expected. 246 247 """ 248 @property 249 def has_representation(self): 250 return super(OptionalRepresentation, self).description is not None 251