Mercurial > p > roundup > code
comparison roundup/rest.py @ 5594:864cf6cb5790 REST-rebased
Added ability to parse HTTP accept header
.. to serve the content type correctly
committer: Ralf Schlatterbeck <rsc@runtux.com>
| author | Chau Nguyen <dangchau1991@yahoo.com> |
|---|---|
| date | Wed, 30 Jan 2019 10:26:35 +0100 |
| parents | 344b6a87dac6 |
| children | 65caddd54da2 |
comparison
equal
deleted
inserted
replaced
| 5593:344b6a87dac6 | 5594:864cf6cb5790 |
|---|---|
| 67 'data': data | 67 'data': data |
| 68 } | 68 } |
| 69 return result | 69 return result |
| 70 return format_object | 70 return format_object |
| 71 | 71 |
| 72 def parse_accept_header(accept): | |
| 73 """ | |
| 74 Parse the Accept header *accept*, returning a list with 3-tuples of | |
| 75 [(str(media_type), dict(params), float(q_value)),] ordered by q values. | |
| 76 | |
| 77 If the accept header includes vendor-specific types like:: | |
| 78 application/vnd.yourcompany.yourproduct-v1.1+json | |
| 79 | |
| 80 It will actually convert the vendor and version into parameters and | |
| 81 convert the content type into `application/json` so appropriate content | |
| 82 negotiation decisions can be made. | |
| 83 | |
| 84 Default `q` for values that are not specified is 1.0 | |
| 85 | |
| 86 # Based on https://gist.github.com/samuraisam/2714195 | |
| 87 # Also, based on a snipped found in this project: | |
| 88 # https://github.com/martinblech/mimerender | |
| 89 """ | |
| 90 result = [] | |
| 91 for media_range in accept.split(","): | |
| 92 parts = media_range.split(";") | |
| 93 media_type = parts.pop(0).strip() | |
| 94 media_params = [] | |
| 95 # convert vendor-specific content types into something useful (see | |
| 96 # docstring) | |
| 97 typ, subtyp = media_type.split('/') | |
| 98 # check for a + in the sub-type | |
| 99 if '+' in subtyp: | |
| 100 # if it exists, determine if the subtype is a vendor-specific type | |
| 101 vnd, sep, extra = subtyp.partition('+') | |
| 102 if vnd.startswith('vnd'): | |
| 103 # and then... if it ends in something like "-v1.1" parse the | |
| 104 # version out | |
| 105 if '-v' in vnd: | |
| 106 vnd, sep, rest = vnd.rpartition('-v') | |
| 107 if len(rest): | |
| 108 # add the version as a media param | |
| 109 try: | |
| 110 version = media_params.append(('version', | |
| 111 float(rest))) | |
| 112 except ValueError: | |
| 113 version = 1.0 # could not be parsed | |
| 114 # add the vendor code as a media param | |
| 115 media_params.append(('vendor', vnd)) | |
| 116 # and re-write media_type to something like application/json so | |
| 117 # it can be used usefully when looking up emitters | |
| 118 media_type = '{}/{}'.format(typ, extra) | |
| 119 q = 1.0 | |
| 120 for part in parts: | |
| 121 (key, value) = part.lstrip().split("=", 1) | |
| 122 key = key.strip() | |
| 123 value = value.strip() | |
| 124 if key == "q": | |
| 125 q = float(value) | |
| 126 else: | |
| 127 media_params.append((key, value)) | |
| 128 result.append((media_type, dict(media_params), q)) | |
| 129 result.sort(lambda x, y: -cmp(x[2], y[2])) | |
| 130 return result | |
| 72 | 131 |
| 73 class RestfulInstance(object): | 132 class RestfulInstance(object): |
| 74 """The RestfulInstance performs REST request from the client""" | 133 """The RestfulInstance performs REST request from the client""" |
| 75 | 134 |
| 76 __default_patch_op = "replace" # default operator for PATCH method | 135 __default_patch_op = "replace" # default operator for PATCH method |
| 136 __accepted_content_type = { | |
| 137 "application/json": "json", | |
| 138 "*/*": "json" | |
| 139 # "application/xml": "xml" | |
| 140 } | |
| 141 __default_accept_type = "json" | |
| 77 | 142 |
| 78 def __init__(self, client, db): | 143 def __init__(self, client, db): |
| 79 self.client = client | 144 self.client = client |
| 80 self.db = db | 145 self.db = db |
| 81 | 146 |
| 776 ) | 841 ) |
| 777 return 204, "" | 842 return 204, "" |
| 778 | 843 |
| 779 def dispatch(self, method, uri, input): | 844 def dispatch(self, method, uri, input): |
| 780 """format and process the request""" | 845 """format and process the request""" |
| 781 # PATH is split to multiple pieces | |
| 782 # 0 - rest | |
| 783 # 1 - resource | |
| 784 # 2 - attribute | |
| 785 uri_split = uri.split("/") | |
| 786 resource_uri = uri_split[1] | |
| 787 | |
| 788 # if X-HTTP-Method-Override is set, follow the override method | 846 # if X-HTTP-Method-Override is set, follow the override method |
| 789 headers = self.client.request.headers | 847 headers = self.client.request.headers |
| 790 method = headers.getheader('X-HTTP-Method-Override') or method | 848 method = headers.getheader('X-HTTP-Method-Override') or method |
| 849 | |
| 850 # parse Accept header and get the content type | |
| 851 accept_header = parse_accept_header(headers.getheader('Accept')) | |
| 852 accept_type = "invalid" | |
| 853 for part in accept_header: | |
| 854 if part[0] in self.__accepted_content_type: | |
| 855 accept_type = self.__accepted_content_type[part[0]] | |
| 791 | 856 |
| 792 # get the request format for response | 857 # get the request format for response |
| 793 # priority : extension from uri (/rest/issue.json), | 858 # priority : extension from uri (/rest/issue.json), |
| 794 # header (Accept: application/json, application/xml) | 859 # header (Accept: application/json, application/xml) |
| 795 # default (application/json) | 860 # default (application/json) |
| 796 | |
| 797 # format_header need a priority parser | |
| 798 ext_type = os.path.splitext(urlparse.urlparse(uri).path)[1][1:] | 861 ext_type = os.path.splitext(urlparse.urlparse(uri).path)[1][1:] |
| 799 accept_header = headers.getheader('Accept')[12:] | 862 data_type = ext_type or accept_type or self.__default_accept_type |
| 800 data_type = ext_type or accept_header or "json" | |
| 801 | 863 |
| 802 # check for pretty print | 864 # check for pretty print |
| 803 try: | 865 try: |
| 804 pretty_output = input['pretty'].value.lower() == "true" | 866 pretty_output = input['pretty'].value.lower() == "true" |
| 805 except KeyError: | 867 except KeyError: |
| 817 ) | 879 ) |
| 818 self.client.setHeader( | 880 self.client.setHeader( |
| 819 "Access-Control-Allow-Methods", | 881 "Access-Control-Allow-Methods", |
| 820 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" | 882 "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" |
| 821 ) | 883 ) |
| 884 | |
| 885 # PATH is split to multiple pieces | |
| 886 # 0 - rest | |
| 887 # 1 - resource | |
| 888 # 2 - attribute | |
| 889 uri_split = uri.split("/") | |
| 890 resource_uri = uri_split[1] | |
| 891 | |
| 822 try: | 892 try: |
| 823 class_name, item_id = hyperdb.splitDesignator(resource_uri) | 893 class_name, item_id = hyperdb.splitDesignator(resource_uri) |
| 824 except hyperdb.DesignatorError: | 894 except hyperdb.DesignatorError: |
| 825 class_name = resource_uri | 895 class_name = resource_uri |
| 826 item_id = None | 896 item_id = None |
