Cornices/cornice

Inconsistent content-type received in the response headers for request headers (Accept: */* or No Accept headers) in cornice+pyramid webserver running on python 3.8 alpine linux version

Opened this issue · 1 comments

  • I have a cornice-based pyramid webserver running in Python 3.8(Alpine Linux) and a client calling one of its APIs using python requests without any request headers.
  • Cornice+Pyramid webserver has msgpack(custom renderer added by me) and simplejson(default renderer of cornice) as renderers.
  • Currently, I am getting inconsistent content-type(sometimes 'application/x-msgpack' and sometimes 'application/json') in the response headers.
  • As per cornice documentation, the default renderer is simplejson thus I am expecting it should always convert the response object into json using json renderer factory of pyramid/cornice and send the response to the client with content-type as 'application/json'.
  • Earlier, I used to have the same webserver running on python 2.7 version, where it was always sending response in json format with consistent content-type as 'application/json'.
  • When I am passing request headers as {'Accept': 'application/json'} or {'Accept': 'application/x-msgpack'} then I am getting response as expected with the response been rendered with the appropriate renderer.
  • I want to know the exact reason, why the default renderer being considered by the webserver is changing between msgpack and simplejson.
  • Below are the files/configuration been used:

A. requirements.txt

simplejson==3.10.0
pyramid==1.5.7
chardet==3.0.4
colander==1.0b1
cornice==1.0
msgpack==1.0.4
lz4==4.0.2
lz4tools==1.3.1.2
PyYAML==3.11
SQLAlchemy==1.4.0
psycopg2-binary==2.9.5
enum34==1.1.6
python-dateutil==2.2
pytz==2014.10
requests==2.7.0
waitress==0.8.9
Paste==3.5.2
jsonschema==2.3.0

B. webserver/init.py

# -*- coding: utf-8 -*-

"""Main entry point
"""

import os

from pyramid.config import Configurator
from pyramid.events import NewResponse
from sdk.pyramid.viewutils import MsgpackRendererFactory, notfound, lz4_compress_response


class MsgpackRendererFactory(object):

    def __init__(self, info):
        pass

    def __call__(self, value, system):
        request = system.get('request')
        if request is not None:
            response = request.response
            response.content_type = 'application/x-msgpack'
        return msgpack.dumps(value)


def main(global_config, **settings):
    route_prefix = settings.get('reports.route_prefix')
    config = Configurator(settings=settings, route_prefix=route_prefix)

    config.add_settings(handle_exceptions=False)
    config.add_notfound_view(notfound, append_slash=True)

    config.add_renderer(name='msgpack', factory=MsgpackRendererFactory)

    config.include('cornice')

    return config.make_wsgi_app()

C. webserver/views.py

@resource(path='path_to_the_api')
class ResourceView():

    @view(accept='application/x-msgpack', renderer='msgpack')
    @view(accept='application/json', renderer='simplejson')
    def get(self):
            # Data fetched from DB stored in 'resource' variable
	    resource = .......... # Calling DB to get data
            return {'resources': resource}

D. Client code

url = 'https://X.X.X.X/path_to_webserver_api'
response = requests.get(url)

body = response.json() # works only if the content-type received is 'application/json' else code                 
                       # breaks at this point 
  • I tried upgrading the packages mentioned in the requirements.txt but was not able to resolve the issue I am facing.
  • Also, debugged the package code locally but was not able to find the root cause.
  • Early reply to the issue will be appreciable.

The code that adds an internal view when no content type is passed is:

empty_contenttype = [({'kind': 'content_type', 'value': ''},)]
for predicate_list in predicate_definitions + empty_contenttype:
args = dict(args) # make a copy of the dict to not modify it
# prepare view args by evaluating complex predicates
_mungle_view_args(args, predicate_list)
# We register the same view multiple times with different
# accept / content_type / custom_predicates arguments
config.add_view(view=decorated_view, route_name=route_name,
**args)

I don't know what would cause this inconsistent behavior, and have limited time to investigate.

If you are in a hurry, as a workaround, you could try to add a default request header?

from pyramid.events import NewRequest

def set_default_header(event):
    request = event.request
    request.headers.setdefault("Accept", "application/json")

config.add_subscriber(set_default_header, NewRequest)