def _perform_request(self, request, parser=None, parser_args=None, operation_context=None):
        '''
        Sends the request and return response. Catches HTTPError and hands it
        to error handler
        '''
        operation_context = operation_context or _OperationContext()
        retry_context = RetryContext()

        # Apply the appropriate host based on the location mode
        self._apply_host(request, operation_context, retry_context)

        # Apply common settings to the request
        _update_request(request)

        while(True):
            try:
                try:
                    # Execute the request callback 
                    if self.request_callback:
                        self.request_callback(request)

                    # Add date and auth after the callback so date doesn't get too old and 
                    # authentication is still correct if signed headers are added in the request 
                    # callback. This also ensures retry policies with long back offs 
                    # will work as it resets the time sensitive headers.
                    _add_date_header(request)
                    self.authentication.sign_request(request)

                    # Set the request context
                    retry_context.request = request

                    # Perform the request
                    response = self._httpclient.perform_request(request)

                    # Execute the response callback
                    if self.response_callback:
                        self.response_callback(response)

                    # Set the response context
                    retry_context.response = response

                    # Parse and wrap HTTP errors in AzureHttpError which inherits from AzureException
                    if response.status >= 300:
                        # This exception will be caught by the general error handler
                        # and raised as an azure http exception
                        _http_error_handler(HTTPError(response.status, response.message, response.headers, response.body))

                    # Parse the response
                    if parser:
                        if parser_args:
                            args = [response]
                            args.extend(parser_args)
                            return parser(*args)
                        else:
                            return parser(response)
                    else:
                        return
                except AzureException as ex:
                    raise ex
                except Exception as ex:
                    if sys.version_info >= (3,):
                        # Automatic chaining in Python 3 means we keep the trace
                        raise AzureException(ex.args[0])
                    else:
                        # There isn't a good solution in 2 for keeping the stack trace 
                        # in general, or that will not result in an error in 3
                        # However, we can keep the previous error type and message
                        # TODO: In the future we will log the trace
                        msg = ""
                        if len(ex.args) > 0:
                            msg = ex.args[0]
                        raise AzureException('{}: {}'.format(ex.__class__.__name__, msg))


            except AzureException as ex:
                # Decryption failures (invalid objects, invalid algorithms, data unencrypted in strict mode, etc)
                # will not be resolved with retries.
                if str(ex) == _ERROR_DECRYPTION_FAILURE:
                    raise ex
                # Determine whether a retry should be performed and if so, how 
                # long to wait before performing retry.
                retry_interval = self.retry(retry_context)
                if retry_interval is not None:
                    # Execute the callback
                    if self.retry_callback:
                        self.retry_callback(retry_context)

                    # Sleep for the desired retry interval
                    sleep(retry_interval)
                else:
                    raise ex
            finally:
                # If this is a location locked operation and the location is not set, 
                # this is the first request of that operation. Set the location to 
                # be used for subsequent requests in the operation.
                if operation_context.location_lock and not operation_context.host_location:
                    operation_context.host_location = {retry_context.location_mode: request.host}