The main reason to do content negotiation is that different documents should be provided for humans and for machines. Content Negotiation can be implemented in two different ways:
Accept: text/htmlhttps:example.com/somepath.jsonBoth things can be easily implemented with web frameworks or libraries like pyramid (see below), python liburl2, Java url dispatch libraries. Implementation via apache2 / nginx configuration can only do the basics for redirection, but interferes with program code when developing more complex content negotiation pathes.
But in context of RDF there is an additional differentiation between the resource that is identified with an URI and the information about this resource, that is provided to the human or machine. As the resource can in most cases not provided via the web, e. g. the resource https://dbpedia.org/resource/Eiffel_Tower the URI of a resource should not provide any content. Instead a permanent redirection to the appropriate content for machines or humans should be implemented. Again, there are two different ways of implementation:
/resource/. dbpedia and others use a special path for the resource URIs. Depending on the Accept header in the http request this URIs are redirected to the appropriate URLs for providing human readable or machine readable content. For example calling http://dbpedia.org/resource/Eiffel_Tower from the browser will redirect to http://dbpedia.org/page/Eiffel_Tower.Pyramid provides and easy way to manage content negotiation via the views decorators. One can define different views that return either text/html content or json, or string, or whatever you want. The views get the same decorator with some additional parameters. For example to serve html or json content the following will do the job:
Define a path in __init __.py
from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') # define a route with tablename as obligate part of the path # and pkids as a list of optional path components: e. g. example.com/table/Identification/12/16/8 config.add_route('table', '/table/{tablename}*pkids') # define another route with tablename as obligate part of the path # and pkids as a list of optional path components: e. g. example.com/download/Identification/12/16/8 config.add_route('download', '/download/{tablename}*pkids') # define a route for content negotiation by file extension config.add_route('file', '/file/{tablename}*pkids.{ext}') [...] config.scan('.views') return config.make_wsgi_app()
Define the views and their decorators in views.py:
[...] # define a decorator that handles requests with Accept: text/html in header @view_config(route_name='table', accept='text/html', renderer='tablelist.pt') def tablelist(self): # some code that return html content for the renderer tablelist.pt [...] # define a decorator that handles requests with Accept: application/json in header @view_config(route_name='table', accept='application/json', renderer='json') def tablelistJSON(self): # some code that return json content for the renderer json [...] # define a decorator that handles a request with Accept: text/html in header but handles the route download that # opens a download dialog and provides the json content as string @view_config(route_name='download', request_method='GET', accept='text/html', renderer = 'string') def tablelistdownload(self): # some code that returns the json data with pretty formatting in string format [...] # define a decorator that does content negotiation on the extension given in the last part of the path @view_config(route_name='file', request_method='GET') def tablelistfile(self): # this view is only called when an extension with .ext exists at the end of the URL path # the different extensions must be handled via the matchdict construct of python pyramid # I am not sure that *pkids.{ext} work together, otherwise .{ext} must be called with defined pathes # in __init__.py like this: config.add_route('file', '/file/{tablename}/{pkid1}/{pkid2}/{pkid3}.{ext}') tablename = self.request.matchdict['tablename'] pkids = self.request.matchdict['pkids'] ext = self.request.matchdict['ext'] # some code that returns the data in the requested format