Mercurial > p > roundup > code
comparison roundup/rest.py @ 5744:d4de45cde106
Accept header parsing fixes. Now return first acceptable match rather
than last. If not acceptable match in accept, 406 error returns list
of acceptable types as text string. application/xml is listed in
acceptable types only if dicttoxml is installed. Handle q > 1.0 by
demoting q factor to 0.0001 making it unusable.
Test cases for all this code. XML is commented out as we don't install
dicttoxml.py.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Wed, 29 May 2019 22:18:46 -0400 |
| parents | abbea26a11df |
| children | dd709ea29899 |
comparison
equal
deleted
inserted
replaced
| 5743:60299cd36670 | 5744:d4de45cde106 |
|---|---|
| 239 (key, value) = part.lstrip().split("=", 1) | 239 (key, value) = part.lstrip().split("=", 1) |
| 240 key = key.strip() | 240 key = key.strip() |
| 241 value = value.strip() | 241 value = value.strip() |
| 242 if key == "q": | 242 if key == "q": |
| 243 q = float(value) | 243 q = float(value) |
| 244 if q > 1.0: | |
| 245 # Not sure what to do here. Can't find spec | |
| 246 # about how to handle q > 1.0. Since invalid | |
| 247 # I choose to make it lowest in priority. | |
| 248 pass | |
| 249 q = 0.0001 | |
| 244 else: | 250 else: |
| 245 media_params.append((key, value)) | 251 media_params.append((key, value)) |
| 246 result.append((media_type, dict(media_params), q)) | 252 result.append((media_type, dict(media_params), q)) |
| 247 result.sort(key=lambda x: x[2], reverse=True) | 253 result.sort(key=lambda x: x[2], reverse=True) |
| 248 return result | 254 return result |
| 335 | 341 |
| 336 __default_patch_op = "replace" # default operator for PATCH method | 342 __default_patch_op = "replace" # default operator for PATCH method |
| 337 __accepted_content_type = { | 343 __accepted_content_type = { |
| 338 "application/json": "json", | 344 "application/json": "json", |
| 339 "*/*": "json", | 345 "*/*": "json", |
| 340 "application/xml": "xml" | |
| 341 } | 346 } |
| 342 __default_accept_type = "json" | 347 __default_accept_type = "json" |
| 343 | 348 |
| 344 __default_api_version = 1 | 349 __default_api_version = 1 |
| 345 __supported_api_versions = [ 1 ] | 350 __supported_api_versions = [ 1 ] |
| 358 self.actions = dict (retire = actions.Retire, restore = actions.Restore) | 363 self.actions = dict (retire = actions.Retire, restore = actions.Restore) |
| 359 | 364 |
| 360 # note TRACKER_WEB ends in a / | 365 # note TRACKER_WEB ends in a / |
| 361 self.base_path = '%srest' % (self.db.config.TRACKER_WEB) | 366 self.base_path = '%srest' % (self.db.config.TRACKER_WEB) |
| 362 self.data_path = self.base_path + '/data' | 367 self.data_path = self.base_path + '/data' |
| 368 | |
| 369 if dicttoxml: # add xml if supported | |
| 370 self.__accepted_content_type["application/xml"] = "xml" | |
| 363 | 371 |
| 364 def props_from_args(self, cl, args, itemid=None, skip_protected=True): | 372 def props_from_args(self, cl, args, itemid=None, skip_protected=True): |
| 365 """Construct a list of properties from the given arguments, | 373 """Construct a list of properties from the given arguments, |
| 366 and return them after validation. | 374 and return them after validation. |
| 367 | 375 |
| 1727 logger.info( | 1735 logger.info( |
| 1728 'Ignoring X-HTTP-Method-Override using %s request on %s', | 1736 'Ignoring X-HTTP-Method-Override using %s request on %s', |
| 1729 method.upper(), uri) | 1737 method.upper(), uri) |
| 1730 | 1738 |
| 1731 # parse Accept header and get the content type | 1739 # parse Accept header and get the content type |
| 1740 # Acceptable types ordered with preferred one first | |
| 1741 # in list. | |
| 1732 accept_header = parse_accept_header(headers.get('Accept')) | 1742 accept_header = parse_accept_header(headers.get('Accept')) |
| 1733 accept_type = self.__default_accept_type | 1743 if not accept_header: |
| 1744 accept_type = self.__default_accept_type | |
| 1745 else: | |
| 1746 accept_type = None | |
| 1734 for part in accept_header: | 1747 for part in accept_header: |
| 1748 if accept_type: | |
| 1749 # we accepted the best match, stop searching for | |
| 1750 # lower quality matches. | |
| 1751 break | |
| 1735 if part[0] in self.__accepted_content_type: | 1752 if part[0] in self.__accepted_content_type: |
| 1736 accept_type = self.__accepted_content_type[part[0]] | 1753 accept_type = self.__accepted_content_type[part[0]] |
| 1737 # Version order: | 1754 # Version order: |
| 1738 # 1) accept header version=X specifier | 1755 # 1) accept header version=X specifier |
| 1739 # application/vnd.x.y; version=1 | 1756 # application/vnd.x.y; version=1 |
| 1760 # get the request format for response | 1777 # get the request format for response |
| 1761 # priority : extension from uri (/rest/data/issue.json), | 1778 # priority : extension from uri (/rest/data/issue.json), |
| 1762 # header (Accept: application/json, application/xml) | 1779 # header (Accept: application/json, application/xml) |
| 1763 # default (application/json) | 1780 # default (application/json) |
| 1764 ext_type = os.path.splitext(urlparse(uri).path)[1][1:] | 1781 ext_type = os.path.splitext(urlparse(uri).path)[1][1:] |
| 1765 data_type = ext_type or accept_type | 1782 data_type = ext_type or accept_type or "invalid" |
| 1766 | 1783 |
| 1767 if ( ext_type ): | 1784 if ( ext_type ): |
| 1768 # strip extension so uri make sense | 1785 # strip extension so uri make sense |
| 1769 # .../issue.json -> .../issue | 1786 # .../issue.json -> .../issue |
| 1770 uri = uri[:-( len(ext_type) + 1 )] | 1787 uri = uri[:-( len(ext_type) + 1 )] |
| 1891 else: | 1908 else: |
| 1892 # FIXME?? consider moving this earlier. We should | 1909 # FIXME?? consider moving this earlier. We should |
| 1893 # error out before doing any work if we can't | 1910 # error out before doing any work if we can't |
| 1894 # display acceptable output. | 1911 # display acceptable output. |
| 1895 self.client.response_code = 406 | 1912 self.client.response_code = 406 |
| 1896 output = "Content type is not accepted by client" | 1913 output = ( "Requested content type is not available.\n" |
| 1914 "Acceptable types: %s"%( | |
| 1915 ", ".join(self.__accepted_content_type.keys()))) | |
| 1897 | 1916 |
| 1898 # Make output json end in a newline to | 1917 # Make output json end in a newline to |
| 1899 # separate from following text in logs etc.. | 1918 # separate from following text in logs etc.. |
| 1900 return bs2b(output + "\n") | 1919 return bs2b(output + "\n") |
| 1901 | 1920 |
