Mercurial > p > roundup > code
diff 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 |
line wrap: on
line diff
--- a/roundup/rest.py Wed Jan 30 10:26:35 2019 +0100 +++ b/roundup/rest.py Wed Jan 30 10:26:35 2019 +0100 @@ -69,11 +69,76 @@ return result return format_object +def parse_accept_header(accept): + """ + Parse the Accept header *accept*, returning a list with 3-tuples of + [(str(media_type), dict(params), float(q_value)),] ordered by q values. + + If the accept header includes vendor-specific types like:: + application/vnd.yourcompany.yourproduct-v1.1+json + + It will actually convert the vendor and version into parameters and + convert the content type into `application/json` so appropriate content + negotiation decisions can be made. + + Default `q` for values that are not specified is 1.0 + + # Based on https://gist.github.com/samuraisam/2714195 + # Also, based on a snipped found in this project: + # https://github.com/martinblech/mimerender + """ + result = [] + for media_range in accept.split(","): + parts = media_range.split(";") + media_type = parts.pop(0).strip() + media_params = [] + # convert vendor-specific content types into something useful (see + # docstring) + typ, subtyp = media_type.split('/') + # check for a + in the sub-type + if '+' in subtyp: + # if it exists, determine if the subtype is a vendor-specific type + vnd, sep, extra = subtyp.partition('+') + if vnd.startswith('vnd'): + # and then... if it ends in something like "-v1.1" parse the + # version out + if '-v' in vnd: + vnd, sep, rest = vnd.rpartition('-v') + if len(rest): + # add the version as a media param + try: + version = media_params.append(('version', + float(rest))) + except ValueError: + version = 1.0 # could not be parsed + # add the vendor code as a media param + media_params.append(('vendor', vnd)) + # and re-write media_type to something like application/json so + # it can be used usefully when looking up emitters + media_type = '{}/{}'.format(typ, extra) + q = 1.0 + for part in parts: + (key, value) = part.lstrip().split("=", 1) + key = key.strip() + value = value.strip() + if key == "q": + q = float(value) + else: + media_params.append((key, value)) + result.append((media_type, dict(media_params), q)) + result.sort(lambda x, y: -cmp(x[2], y[2])) + return result class RestfulInstance(object): """The RestfulInstance performs REST request from the client""" __default_patch_op = "replace" # default operator for PATCH method + __accepted_content_type = { + "application/json": "json", + "*/*": "json" + # "application/xml": "xml" + } + __default_accept_type = "json" def __init__(self, client, db): self.client = client @@ -778,26 +843,23 @@ def dispatch(self, method, uri, input): """format and process the request""" - # PATH is split to multiple pieces - # 0 - rest - # 1 - resource - # 2 - attribute - uri_split = uri.split("/") - resource_uri = uri_split[1] - # if X-HTTP-Method-Override is set, follow the override method headers = self.client.request.headers method = headers.getheader('X-HTTP-Method-Override') or method + # parse Accept header and get the content type + accept_header = parse_accept_header(headers.getheader('Accept')) + accept_type = "invalid" + for part in accept_header: + if part[0] in self.__accepted_content_type: + accept_type = self.__accepted_content_type[part[0]] + # get the request format for response # priority : extension from uri (/rest/issue.json), # header (Accept: application/json, application/xml) # default (application/json) - - # format_header need a priority parser ext_type = os.path.splitext(urlparse.urlparse(uri).path)[1][1:] - accept_header = headers.getheader('Accept')[12:] - data_type = ext_type or accept_header or "json" + data_type = ext_type or accept_type or self.__default_accept_type # check for pretty print try: @@ -819,6 +881,14 @@ "Access-Control-Allow-Methods", "HEAD, OPTIONS, GET, PUT, DELETE, PATCH" ) + + # PATH is split to multiple pieces + # 0 - rest + # 1 - resource + # 2 - attribute + uri_split = uri.split("/") + resource_uri = uri_split[1] + try: class_name, item_id = hyperdb.splitDesignator(resource_uri) except hyperdb.DesignatorError:
