"""Declare HTTP API
This module is responsible for interpreting the HTTP request
parameters and headers. All OAI-PMH specific parameters are
interpreted in controller logic. The controller must be linked to HTTP
API via Tornado WebApplication initialization (see get_app()) and is
used in OAIRouteHandler via self.application.settings['controller'].
"""
import logging
import os.path
from urllib.parse import unquote
from kuha_common.server import (
RequestHandler,
WebApplication,
str_api_endpoint
)
_logger = logging.getLogger(__name__)
[docs]class OAIRouteHandler(RequestHandler):
"""Declares the OAI-PMH HTTP API.
Takes in HTTP verbs GET and POST. Gathers their parameters and
dispatches the parameters, correlation_id HTTP headers and
a tornado request object to the controller.
Dispatching requests to controller
----------------------------------
The dispatch is done by calling the controller's oai_request async method
with parameters: args, correlation_id_header, tornado_request
* args is a list of two-tuples containing request parameters that were submitted
via GET or POST.
* correlation_id_header is a dictionary that can be used as is for further
requests using Tornado's HTTP clients: {'X-REQUEST-ID': <corr_id>}.
* tornado_request is a tornado request object that wraps the current request.
It should be used as a read-only object.
"""
@property
def _ctrl(self):
return self.application.settings['controller']
[docs] async def prepare(self):
"""Prepare response by settings the correct output content type.
"""
await super().prepare()
self.oai_protocol = None
self.set_output_content_type(self.CONTENT_TYPE_XML)
[docs] async def write_output(self, iterable):
"""Write output by iterating the parameter.
:param iterable: output to be written as HTTP response.
"""
for data in iterable:
self.write(data)
async def _dispatch(self, args):
iterable, self.oai_protocol = await self._ctrl.oai_request(
args, self._correlation_id.as_header(), self.request)
await self.write_output(iterable)
[docs] async def get(self):
"""HTTP-GET handler
Gathers request arguments. Calls dispatch. Finishes the response.
"URLs for GET requests have keyword arguments appended to the base URL"
-- http://www.openarchives.org/OAI/openarchivesprotocol.html#ProtocolFeatures
"""
args = []
for key, values in self.request.arguments.items():
for value in values:
arg_value = value.decode('utf-8')
args.append((key, unquote(arg_value)))
await self._dispatch(args)
self.finish()
[docs] async def post(self):
"""HTTP-POST handler
Validates request content type. Gathers request arguments.
Calls dispatch. Finishes the response.
"Keyword arguments are carried in the message body of the HTTP POST.
The Content-Type of the request must be application/x-www-form-urlencoded."
-- http://www.openarchives.org/OAI/openarchivesprotocol.html#ProtocolFeatures
"""
self.assert_request_content_type('application/x-www-form-urlencoded')
args = []
_body = self.request.body.decode('utf-8')
for submitted_arg in _body.split('&'):
if not submitted_arg:
continue
key = submitted_arg.split('=')[0]
value = self.get_body_argument(key)
args.append((key, unquote(value)))
await self._dispatch(args)
self.finish()
[docs]def get_app(api_version, controller, app_class=None):
"""Setup routes and return initialized Tornado web application.
:param str api_version: HTTP Api version gets prepended to routes.
:param controller: Controller logic for HTTP API.
:param app_class: Use custom WebApplication class. Defaults to kuha_common.server.WebApplication.
:returns: Tornado web application.
:rtype: :obj:`tornado.web.Application`
"""
handlers = [
(str_api_endpoint(api_version, r"oai"), OAIRouteHandler),
]
settings = {'controller': controller}
if controller.stylesheet_url.startswith('/'):
static_url_prefix = '/'.join(controller.stylesheet_url.split('/')[:-1]) + '/'
settings.update({'static_path': os.path.join(os.path.dirname(__file__), 'static'),
'static_url_prefix': static_url_prefix})
app_class = app_class or WebApplication
web_app = app_class(handlers=handlers, **settings)
return web_app