Ejemplo n.º 1
0
class AddressServer(Application):

    """
    This class is the gunicorn wrapper to serve up bottle.

    Strongly borrowed from:
    http://damianzaremba.co.uk/2012/08/running-a-wsgi-
    app-via-gunicorn-from-python/

    """

    def __init__(self, options={}):
        self.usage = None
        self.callable = None
        self.prog = None
        self.options = options
        self.do_load_config()

        self.setup_cork()

        super(AddressServer, self).__init__()
        self.app = Bottle()
        self.add_routes()
        self.add_middleware()

    def setup_cork(self):
        """Set up cork using environment variables."""

        EMAIL = os.environ.get('EMAIL_SENDER')
        EMAIL_PASS = os.environ.get('EMAIL_PASSWORD')
        self.MONGO_DB = os.environ.get('MONGOHQ_DB')
        self.MONGO_URL = os.environ.get('MONGOHQ_URL')
        mb = MongoDBBackend(self.MONGO_DB, self.MONGO_URL)
        self.loginPlugin = Cork(backend=mb, email_sender=EMAIL,
                                smtp_url='starttls://' + EMAIL + ':' +
                                EMAIL_PASS + '@smtp.gmail.com:587')

    def add_middleware(self):
        """Set up the session middleware."""

        ENCRYPT_KEY = os.environ.get('ENCRYPT_KEY')
        session_opts = {
            'session.cookie_expires': True,
            'session.encrypt_key': ENCRYPT_KEY,
            'session.httponly': True,
            'session.timeout': 3600 * 24,  # 1 day
            'session.type': 'cookie',
            'session.validate_key': True,
        }
        self.app = SessionMiddleware(self.app, session_opts)

    def add_routes(self):
        """Add all the application routes."""

        self.app.route(LOGIN_PATH, 'GET', callback=get_login)
        self.app.route(LOGIN_PATH, 'POST', callback=post_login,
                       apply=self.add_login_plugin)
        self.app.route('/logout', 'GET', callback=logout,
                       apply=self.add_login_plugin)
        self.app.route('/register', 'GET', callback=get_register,
                       apply=self.add_login_plugin)
        self.app.route('/register', 'POST', callback=post_register,
                       apply=self.add_login_plugin)
        self.app.route(VALIDATE_REGISTRATION_PATH + '/<registration_code>',
                       'GET', callback=validate_registration,
                       apply=self.add_login_plugin)
        self.app.route(CHANGE_PASSWORD_PATH + '/<reset_code>', 'GET',
                       callback=get_change_password)
        self.app.route(CHANGE_PASSWORD_PATH, 'POST',
                       callback=post_change_password,
                       apply=self.add_login_plugin)
        self.app.route('/reset_password', 'GET', callback=get_reset_password)
        self.app.route('/reset_password', 'POST', callback=post_reset_password,
                       apply=self.add_login_plugin)
        self.app.route('/', 'GET', callback=index, apply=self.check_login)
        self.app.route('/addresses', 'GET', callback=get_addresses,
                       apply=self.check_login)
        self.app.route('/addresses', 'POST', callback=post_addresses,
                       apply=self.check_login)
        self.app.route('/addresses', 'PUT', callback=put_addresses,
                       apply=self.check_login)
        self.app.route('/addresses/<deleteId>', 'DELETE',
                       callback=delete_addresses, apply=self.check_login)
        self.app.route('/csv', 'GET', callback=csv_export,
                       apply=self.check_login)
        self.app.route('/christmas_card', 'GET',
                       callback=christmas_card_csv_export,
                       apply=self.check_login)
#        self.app.route('/import_csv', 'GET', callback=csv_import,
#                       apply=self.check_login)

        self.app.route('/js/<filename>', 'GET', callback=js_static)
        self.app.route('/css/<filename>', 'GET', callback=css_static)

    def init(self, *args):
        """Add any options passed in to the config.

        :param *args: the arguments of the application
        :returns: config object
        :rtype: dict

        """

        cfg = {}
        for k, v in self.options.items():
            if k.lower() in self.cfg.settings and v is not None:
                cfg[k.lower()] = v
        return cfg

    def load(self):
        """Load and return the bottle app."""

        return self.app

    @hook('before_request')
    def check_login(self, fn):
        """Hook for checking the login before doing logic.

        :param fn: the function to call if the user is logged in
        :returns: the wrapped function
        :rtype: def

        """

        def check_uid(**kwargs):
            self.loginPlugin.require(fail_redirect=LOGIN_PATH)
            kwargs["helper"] = AddressModel(self.MONGO_URL, self.MONGO_DB)
            kwargs["userName"] = self.loginPlugin.current_user.username
            return fn(**kwargs)
        return check_uid

    @hook('before_request')
    def add_login_plugin(self, fn):
        """Hook for adding the plugin information.

        :param fn: the function to pass the plugin
        :returns: the wrapped function
        :rtype: def

        """

        def add_plugin(**kwargs):
            kwargs["loginPlugin"] = self.loginPlugin
            return fn(**kwargs)
        return add_plugin
class HttpEndpoint(IOpenable, IConfigurable, IReferenceable):
    """
    Used for creating HTTP endpoints. An endpoint is a URL, at which a given service can be accessed by a client.

    ### Configuration parameters ###
        Parameters to pass to the :func:`configure` method for component configuration:

        - cors_headers - a comma-separated list of allowed CORS headers
        - cors_origins - a comma-separated list of allowed CORS origins

        - connection(s) - the connection resolver's connections;
            - "connection.discovery_key" - the key to use for connection resolving in a discovery service;
            - "connection.protocol" - the connection's protocol;
            - "connection.host" - the target host;
            - "connection.port" - the target port;
            - "connection.uri" - the target URI.

        - credential - the HTTPS credentials:
            - "credential.ssl_key_file" - the SSL private key in PEM
            - "credential.ssl_crt_file" - the SSL certificate in PEM
            - "credential.ssl_ca_file" - the certificate authorities (root cerfiticates) in PEM


    ### References ###
        A logger, counters, and a connection resolver can be referenced by passing the following references to the object's :func:`set_references` method:
            - `*:logger:*:*:1.0`           (optional) :class:`ILogger <pip_services3_components.log.ILogger.ILogger>` components to pass log messages
            - `*:counters:*:*:1.0`         (optional) :class:`ICounters <pip_services3_components.count.ICounters.ICounters>` components to pass collected measurements
            - `*:discovery:*:*:1.0`        (optional) :class:`IDiscovery <pip_services3_components.connect.IDiscovery.IDiscovery>` services to resolve connection

    Example:

    .. code-block:: python

        def my_method(_config, _references):
            endpoint = HttpEndpoint()
            if (_config)
                endpoint.configure(_config)
            if (_references)
                endpoint.setReferences(_references)
            # ...

            endpoint.open(correlationId)
            # ...
    """
    _default_config = ConfigParams.from_tuples(
        "connection.protocol", "http", "connection.host", "0.0.0.0",
        "connection.port", 3000, "credential.ssl_key_file", None,
        "credential.ssl_crt_file", None, "credential.ssl_ca_file", None,
        "options.maintenance_enabled", False, "options.request_max_size",
        1024 * 1024, "options.file_max_size", 200 * 1024 * 1024,
        "connection.connect_timeout", 60000, "connection.debug", True)

    _debug = False

    def __init__(self):
        """
        Creates HttpEndpoint
        """
        self.__service = None
        self.__server = None
        self.__maintenance_enabled: bool = False
        self.__file_max_size = 200 * 1024 * 1024
        self.__protocol_upgrade_enabled: bool = False
        self.__uri: str = None

        self.__connection_resolver: HttpConnectionResolver = HttpConnectionResolver(
        )
        self.__logger: CompositeLogger = CompositeLogger()
        self.__counters: CompositeCounters = CompositeCounters()
        self.__registrations: List[IRegisterable] = []
        self.__allowed_headers: List[str] = ["correlation_id"]
        self.__allowed_origins: List[str] = []

    def configure(self, config: ConfigParams):
        """
        Configures this HttpEndpoint using the given configuration parameters.
        - connection(s) - the connection resolver's connections;
            - "connection.discovery_key" - the key to use for connection resolving in a discovery service;
            - "connection.protocol" - the connection's protocol;
            - "connection.host" - the target host;
            - "connection.port" - the target port;
            - "connection.uri" - the target URI.

        :param config: configuration parameters, containing a "connection(s)" section.
        """
        config = config.set_defaults(self._default_config)
        self.__connection_resolver.configure(config)

        bottle.BaseRequest.MEMFILE_MAX = config.get_as_long(
            'options.request_max_size')
        self.__file_max_size = config.get_as_long_with_default(
            'options.file_max_size', self.__file_max_size)
        self.__maintenance_enabled = config.get_as_boolean_with_default(
            'options.maintenance_enabled', self.__maintenance_enabled)
        self.__protocol_upgrade_enabled = config.get_as_boolean_with_default(
            'options.protocol_upgrade_enabled',
            self.__protocol_upgrade_enabled)
        self._debug = config.get_as_boolean_with_default(
            'options.debug', self._debug)

        headers = config.get_as_string_with_default("cors_headers",
                                                    "").split(",")
        for header in headers:
            if header != '':
                self.__allowed_headers = list(
                    filter(lambda h: h != header, self.__allowed_headers))
                self.__allowed_headers.append(header)

        origins = config.get_as_string_with_default("cors_origins",
                                                    "").split(',')
        for origin in origins:
            origin = origin.strip()
            if origin != '':
                self.__allowed_origins = list(
                    filter(lambda h: h != origin, self.__allowed_origins))
                self.__allowed_origins.append(origin)

    def set_references(self, references: IReferences):
        """
        Sets references to this endpoint's logger, counters, and connection resolver.

        - *:logger:*:*:1.0           (optional) :class:`ILogger <pip_services3_components.log.ILogger.ILogger>` components to pass log messages
        - *:counters:*:*:1.0         (optional) :class:`ICounters <pip_services3_components.count.ICounters.ICounters>` components to pass collected measurements
        - *:discovery:*:*:1.0        (optional) :class:`IDiscovery <pip_services3_components.connect.IDiscovery.IDiscovery>` services to resolve connection

        :param references: an IReferences object, containing references to a logger, counters, and a connection resolver.
        """
        self.__logger.set_references(references)
        self.__counters.set_references(references)
        self.__connection_resolver.set_references(references)

    def is_open(self) -> bool:
        """
        Checks if the component is opened.

        :return: whether or not this endpoint is open with an actively listening REST server.
        """
        return not (self.__server is None)

    def open(self, correlation_id: Optional[str]):
        """
        Opens a connection using the parameters resolved by the referenced connection resolver and creates a REST server (service) using the set options and parameters.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        if self.is_open():
            return

        connection = self.__connection_resolver.resolve(correlation_id)
        if connection is None:
            raise ConfigException(correlation_id, "NO_CONNECTION",
                                  "Connection for REST client is not defined")
        self.__uri = connection.get_as_string('uri')

        # verify https with bottle

        certfile = None
        keyfile = None

        if connection.get_as_string_with_default('protocol',
                                                 'http') == 'https':
            certfile = connection.get_as_nullable_string('ssl_crt_file')
            keyfile = connection.get_as_nullable_string('ssl_key_file')

        # Create instance of bottle application
        self.__service = SessionMiddleware(
            bottle.Bottle(catchall=True, autojson=True)).app

        self.__service.config['catchall'] = True
        self.__service.config['autojson'] = True

        # Enable CORS requests
        self.__service.add_hook('after_request', self.__enable_cors)

        self.__service.add_hook('after_request', self.__do_maintance)
        self.__service.add_hook('after_request', self.__no_cache)
        self.__service.add_hook('before_request', self.__add_compatibility)

        # Register routes
        # self.__perform_registrations()

        def start_server():
            self.__service.run(server=self.__server, debug=self._debug)

        # self.__perform_registrations()

        host = connection.get_as_string('host')
        port = connection.get_as_integer('port')
        # Starting service
        try:
            self.__server = SSLCherryPyServer(host=host,
                                              port=port,
                                              certfile=certfile,
                                              keyfile=keyfile)

            # Start server in thread
            Thread(target=start_server).start()
            # Time for start server
            time.sleep(0.01)

            # Give 2 sec for initialization
            self.__connection_resolver.register(correlation_id)
            self.__logger.debug(
                correlation_id,
                f"Opened REST service at {self.__uri}",
            )
            self.__perform_registrations()
        except Exception as ex:
            self.__server = None

            raise ConnectionException(correlation_id, 'CANNOT_CONNECT', 'Opening REST service failed') \
                .wrap(ex).with_details('url', self.__uri)

    def close(self, correlation_id: Optional[str]):
        """
        Closes this endpoint and the REST server (service) that was opened earlier.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        try:
            if not (self.__server is None):
                self.__server.shutdown()
                self.__service.close()
                self.__logger.debug(correlation_id,
                                    f"Closed REST service at {self.__uri}")

            self.__server = None
            self.__service = None
            self.__uri = None
        except Exception as ex:
            self.__logger.warn(correlation_id,
                               "Failed while closing REST service: " + str(ex))

    def register(self, registration: IRegisterable):
        """
        Registers a registerable object for dynamic endpoint discovery.

        :param registration: the registration to add.
        """
        self.__registrations.append(registration)

    def unregister(self, registration: IRegisterable):
        """
        Unregisters a registerable object, so that it is no longer used in dynamic endpoint discovery.

        :param registration: the registration to remove.
        """
        self.__registrations.remove(registration)

    def __perform_registrations(self):
        for registration in self.__registrations:
            registration.register()

    def __fix_route(self, route: str) -> str:
        if route is not None and len(route) > 0:
            if route[0] != '/':
                route = f'/{route}'
            return route

        return ''

    def register_route(self, method: str, route: str, schema: Schema,
                       handler: Callable):
        """
        Registers an action in this objects REST server (service) by the given method and route.

        :param method: the HTTP method of the route.

        :param route: the route to register in this object's REST server (service).

        :param schema: the schema to use for parameter validation.

        :param handler: the action to perform at the given route.
        """
        method = method.upper()
        # if method == 'DELETE':
        #     method = 'DEL'

        route = self.__fix_route(route)

        def wrapper(*args, **kwargs):
            try:
                if isinstance(schema, Schema):
                    params = self.__get_data() or {}
                    params.update(kwargs)
                    correlation_id = None if not params else params.get(
                        'correlation_id')
                    schema.validate_and_throw_exception(
                        correlation_id, params, False)

                return handler(*args, **kwargs)
            except Exception as ex:
                # hack the redirect response in bottle
                if isinstance(ex, bottle.HTTPResponse):
                    handler(*args, **kwargs)
                return HttpResponseSender.send_error(ex)

        self.__service.route(route, method, wrapper)

    def __get_data(self) -> Optional[dict]:
        result = {}
        if request.json or request.query:
            for k, v in request.query.dict.items():
                result[k] = ''.join(v)
            if request.json is not None and request.json != 'null':
                result.update(request.json if not isinstance(
                    request.json, str) else json.loads(request.json))
            return result
        else:
            return None

    def __enable_cors(self):
        response.headers['Access-Control-Max-Age'] = '5'
        response.headers['Access-Control-Allow-Origin'] = ', '.join(
            self.__allowed_origins)
        response.headers[
            'Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = ', '.join(
            self.__allowed_headers)

    def __do_maintance(self):
        """
        :return: maintenance error code
        """
        # Make this more sophisticated
        if self.__maintenance_enabled:
            response.headers['Retry-After'] = 3600
            response.status = 503

    def __no_cache(self):
        """
        Prevents IE from caching REST requests
        """
        response.headers[
            'Cache-Control'] = 'no-cache, no-store, must-revalidate'
        response.headers['Pragma'] = 'no-cache'
        response.headers['Expires'] = 0

    def __add_compatibility(self):
        def inner(name):
            if request.query:
                param = request.query[name]
                if param:
                    return param
            if request.body:
                param = request.json[name]
                if param:
                    return param
            if request.params:
                param = request.params[name]
                if param:
                    return param

            return None

        request['param'] = inner
        request['route'] = {'params': request.params}

    def get_param(self, param, default=None):
        return request.params.get(param, default)

    def get_correlation_id(self) -> Optional[str]:
        """
        Returns correlationId from request

        :returns: Returns correlationId from request
        """
        correlation_id = bottle.request.query.get('correlation_id')
        if correlation_id is None or correlation_id == '':
            correlation_id = bottle.request.headers.get('correlation_id')
        return correlation_id

    def register_route_with_auth(self, method: str, route: str, schema: Schema,
                                 authorize: Callable, action: Callable):
        """
        Registers an action with authorization in this objects REST server (service)
        by the given method and route.

        :param method: the HTTP method of the route.
        :param route: the route to register in this object's REST server (service).
        :param schema: the schema to use for parameter validation.
        :param authorize: the authorization interceptor
        :param action: the action to perform at the given route.
        """
        def action_with_authorize(*args, **kwargs):
            # hack to pass the parameters in authorizer
            bottle.request.params['kwargs'] = kwargs
            # bottle.request.params['args'] = args

            authorize()
            return next_action(*args, **kwargs)

        if authorize:
            next_action = action
            action = action_with_authorize

        self.register_route(method, route, schema, action)

    def register_interceptor(self, route: str, action: Callable):
        """
        Registers a middleware action for the given route.

        :param route: the route to register in this object's REST server (service).
        :param action: the middleware action to perform at the given route.
        """
        route = self.__fix_route(route)

        def intercept_handler():
            match = re.match('.*' + route, request.url) is not None
            if route is not None and route != '' and not match:
                pass
            else:
                return action()

        self.__service.add_hook('before_request', intercept_handler)
class HttpEndpoint(IOpenable, IConfigurable, IReferenceable):
    """
    Used for creating HTTP endpoints. An endpoint is a URL, at which a given service can be accessed by a client.

    ### Configuration parameters ###

    Parameters to pass to the [[configure]] method for component configuration:

    - connection(s) - the connection resolver's connections;
        - "connection.discovery_key" - the key to use for connection resolving in a discovery service;
        - "connection.protocol" - the connection's protocol;
        - "connection.host" - the target host;
        - "connection.port" - the target port;
        - "connection.uri" - the target URI.

    ### References ###

    A logger, counters, and a connection resolver can be referenced by passing the following references to the object's [[setReferences]] method:

    - *:logger:*:*:1.0         (optional) ILogger components to pass log messages
    - *:counters:*:*:1.0         (optional) ICounters components to pass collected measurements
    - *:discovery:*:*:1.0        (optional) IDiscovery services to resolve connection

    Example:
        def my_method(_config, _references):
            endpoint = HttpEndpoint()
            if (_config)
                endpoint.configure(_config)
            if (_references)
                endpoint.setReferences(_references)
            ...

            endpoint.open(correlationId)
            ...
    """
    _default_config = None
    _connection_resolver = None
    _logger = None
    _counters = None
    _registrations = None
    _service = None
    _server = None
    _debug = False
    _uri = None
    _file_max_size = 200 * 1024 * 1024
    _maintenance_enabled = False
    _protocol_upgrade_enabled = False

    def __init__(self):
        """
        Creates HttpEndpoint
        """
        self._default_config = ConfigParams.from_tuples(
            "connection.protocol", "http", "connection.host", "0.0.0.0",
            "connection.port", 3000, "credential.ssl_key_file", None,
            "credential.ssl_crt_file", None, "credential.ssl_ca_file", None,
            "options.maintenance_enabled", False, "options.request_max_size",
            1024 * 1024, "options.file_max_size", 200 * 1024 * 1024,
            "connection.connect_timeout", 60000, "connection.debug", True)
        self._connection_resolver = HttpConnectionResolver()
        self._logger = CompositeLogger()
        self._counters = CompositeCounters()
        self._registrations = []

    def configure(self, config):
        """
        Configures this HttpEndpoint using the given configuration parameters.

        - connection(s) - the connection resolver's connections;
            - "connection.discovery_key" - the key to use for connection resolving in a discovery service;
            - "connection.protocol" - the connection's protocol;
            - "connection.host" - the target host;
            - "connection.port" - the target port;
            - "connection.uri" - the target URI.

        :param config: configuration parameters, containing a "connection(s)" section.
        """
        config = config.set_defaults(self._default_config)
        self._connection_resolver.configure(config)
        self._file_max_size = config.get_as_boolean_with_default(
            'options.file_max_size', self._file_max_size)
        self._maintenance_enabled = config.get_as_long_with_default(
            'options.maintenance_enabled', self._maintenance_enabled)
        self._protocol_upgrade_enabled = config.get_as_boolean_with_default(
            'options.protocol_upgrade_enabled', self._protocol_upgrade_enabled)
        self._debug = config.get_as_boolean_with_default(
            'connection.debug', self._debug)

    def set_references(self, references):
        """
        Sets references to this endpoint's logger, counters, and connection resolver.

        - *:logger:*:*:1.0         (optional) ILogger components to pass log messages
        - *:counters:*:*:1.0         (optional) ICounters components to pass collected measurements
        - *:discovery:*:*:1.0        (optional) IDiscovery services to resolve connection

        :param references: an IReferences object, containing references to a logger, counters, and a connection resolver.
        """
        self._logger.set_references(references)
        self._counters.set_references(references)
        self._connection_resolver.set_references(references)

    def is_opened(self):
        """
        Checks if the component is opened.

        :return: whether or not this endpoint is open with an actively listening REST server.
        """
        return not (self._server is None)

    def open(self, correlation_id):
        """
        Opens a connection using the parameters resolved by the referenced connection resolver and creates a REST server (service) using the set options and parameters.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        if self.is_opened():
            return

        connection = self._connection_resolver.resolve(correlation_id)
        if connection is None:
            raise ConfigException(correlation_id, "NO_CONNECTION",
                                  "Connection for REST client is not defined")
        self._uri = connection.get_uri()

        # verify https with bottle

        certfile = None
        keyfile = None

        if connection.get_protocol('http') == 'https':
            certfile = connection.get_as_nullable_string('ssl_crt_file')
            keyfile = connection.get_as_nullable_string('ssl_key_file')

        # Create instance of bottle application
        self._service = SessionMiddleware(
            bottle.Bottle(catchall=True, autojson=True)).app

        self._service.config['catchall'] = True
        self._service.config['autojson'] = True

        # Enable CORS requests
        self._service.add_hook('after_request', self._enable_cors)
        self._service.route('/', 'OPTIONS', self._options_handler)
        self._service.route('/<path:path>', 'OPTIONS', self._options_handler)

        self._service.add_hook('after_request', self._do_maintance)
        self._service.add_hook('after_request', self._no_cache)
        self._service.add_hook('before_request', self._add_compatibility)

        # Register routes
        # self.perform_registrations()

        def start_server():
            self._service.run(server=self._server, debug=self._debug)

        # self.perform_registrations()

        host = connection.get_host()
        port = connection.get_port()
        # Starting service
        try:
            self._server = SSLCherryPyServer(host=host,
                                             port=port,
                                             certfile=certfile,
                                             keyfile=keyfile)

            # Start server in thread
            Thread(target=start_server).start()

            # Give 2 sec for initialization
            self._connection_resolver.register(correlation_id)
            self._logger.debug(
                correlation_id,
                f"Opened REST service at {self._uri}",
            )
            self.perform_registrations()
        except Exception as ex:
            self._server = None

            raise ConnectionException(correlation_id, 'CANNOT_CONNECT', 'Opening REST service failed') \
                .wrap(ex).with_details('url', self._uri)

    def close(self, correlation_id):
        """
        Closes this endpoint and the REST server (service) that was opened earlier.

        :param correlation_id: (optional) transaction id to trace execution through call chain.
        """
        try:
            if not (self._server is None):
                self._server.shutdown()
                self._service.close()
                self._logger.debug(correlation_id, "Closed REST service at %s",
                                   self._uri)

            self._server = None
            self._service = None
            self._uri = None
        except Exception as ex:
            self._logger.warn(correlation_id,
                              "Failed while closing REST service: " + str(ex))

    def register(self, registration):
        """
        Registers a registerable object for dynamic endpoint discovery.

        :param registration: the registration to add.
        """
        self._registrations.append(registration)

    def unregister(self, registration):
        """
        Unregisters a registerable object, so that it is no longer used in dynamic endpoint discovery.

        :param registration: the registration to remove.
        """
        self._registrations.remove(registration)

    def perform_registrations(self):
        for registration in self._registrations:
            registration.register()

    def fix_route(self, route) -> str:
        if route is not None and len(route) > 0:
            if route[0] != '/':
                route = f'/{route}'
            return route

        return ''

    def register_route(self, method, route, schema, handler):
        """
        Registers an action in this objects REST server (service) by the given method and route.

        :param method: the HTTP method of the route.

        :param route: the route to register in this object's REST server (service).

        :param schema: the schema to use for parameter validation.

        :param handler: the action to perform at the given route.
        """
        method = method.upper()
        # if method == 'DELETE':
        #     method = 'DEL'

        route = self.fix_route(route)

        def wrapper(*args, **kwargs):
            try:
                if isinstance(schema, Schema):
                    params = self.get_data()
                    correlation_id = params[
                        'correlation_id'] if 'correlation_id' in params else None
                    error = schema.validate_and_throw_exception(
                        correlation_id, params, False)
                return handler(*args, **kwargs)
            except Exception as ex:
                return HttpResponseSender.send_error(ex)

        self._service.route(route, method, wrapper)

    def get_data(self):
        if request.json:
            return request.json
        else:
            return None

    def _enable_cors(self):
        response.headers['Access-Control-Max-Age'] = '5'
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers[
            'Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS'
        response.headers[
            'Access-Control-Allow-Headers'] = 'Authorization, Origin, Accept, Content-Type, X-Requested-With'

    def _do_maintance(self):
        """
        :return: maintenance error code
        """
        # Make this more sophisticated
        if self._maintenance_enabled:
            response.headers['Retry-After'] = 3600
            response.status = 503

    def _no_cache(self):
        """
        Prevents IE from caching REST requests
        """
        response.headers[
            'Cache-Control'] = 'no-cache, no-store, must-revalidate'
        response.headers['Pragma'] = 'no-cache'
        response.headers['Expires'] = 0

    def _add_compatibility(self):
        def inner(name):
            if request.query:
                param = request.query[name]
                if param:
                    return param
            if request.body:
                param = request.json[name]
                if param:
                    return param
            if request.params:
                param = request.params[name]
                if param:
                    return param

            return None

        request['param'] = inner
        request['route'] = {'params': request.params}

    def _options_handler(self, ath=None):
        return

    def get_param(self, param, default=None):
        return request.params.get(param, default)

    def get_correlation_id(self):
        return request.query.get('correlation_id')

    def register_route_with_auth(self, method, route, schema, authorize,
                                 action):
        """
        Registers an action with authorization in this objects REST server (service)
        by the given method and route.

        :param method: the HTTP method of the route.
        :param route: the route to register in this object's REST server (service).
        :param schema: the schema to use for parameter validation.
        :param authorize: the authorization interceptor
        :param action: the action to perform at the given route.
        """
        if authorize:
            next_action = action
            action = lambda req, res: authorize(
                request, response, next_action(response, response))

        self.register_route(method, route, schema, action)

    def register_interceptor(self, route, action):
        """
        Registers a middleware action for the given route.

        :param route: the route to register in this object's REST server (service).
        :param action: the middleware action to perform at the given route.
        """
        route = self.fix_route(route)

        self._service.add_hook(
            'before_request', lambda: action(request, response)
            if not (route is not None and route != '' and request.url.
                    startswith(route)) else None)