<commit_before># -*- test-case-name: vumi.application.tests.test_rapidsms_relay -*-
from urllib import urlencode
from base64 import b64encode

from twisted.internet.defer import inlineCallbacks
from twisted.web import http
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET

from vumi.application.base import ApplicationWorker
from vumi.utils import http_request_full
from vumi.errors import ConfigError
from vumi import log


class HealthResource(Resource):
    isLeaf = True

    def render_GET(self, request):
        request.setResponseCode(http.OK)
        request.do_not_log = True
        return 'OK'


class SendResource(Resource):
    isLeaf = True

    def __init__(self, application):
        self.application = application
        Resource.__init__(self)

    def finish_request(self, request, msg):
        request.setResponseCode(http.OK)
        request.write(msg.to_json())
        request.finish()

    def render_(self, request):
        log.msg("Send request: %s" % (request,))
        request.setHeader("content-type", "application/json")
        d = self.application.handle_raw_outbound_message(request)
        d.addCallback(lambda msg: self.finish_request(request, msg))
        return NOT_DONE_YET

    def render_PUT(self, request):
        return self.render_(request)

    def render_GET(self, request):
        return self.render_(request)

    def render_POST(self, request):
        return self.render_(request)


class RapidSMSRelay(ApplicationWorker):
    """Application that relays messages to RapidSMS.

    RapidSMS relay configuration options:

    :param str rapidsms_url:
        URL of the rapidsms http backend.
    :param str web_path:
        Path to listen for outbound messages from RapidSMS on.
    :param str web_port:
        Port to listen for outbound messages from RapidSMS on.
    :param str username:
        Username to use for the `rapidsms_url` (default: no authentication).
    :param str password:
        Password to use for the `rapidsms_url` (default: none).
    :param str auth_method:
        Authentication method to use with `rapidsms_url` (default: 'basic').
        The 'basic' method is currently the only available method.
    :param str http_method:
        HTTP request method to use for the `rapidsms_url` (default: POST)

    A RapidSMS relay requires a `send_to` configuration section for the
    `default` send_to tag.
    """

    SEND_TO_TAGS = frozenset(['default'])

    def validate_config(self):
        self.rapidsms_url = self.config['rapidsms_url']
        self.web_path = self.config['web_path']
        self.web_port = int(self.config['web_port'])

        self.supported_auth_methods = {
            'basic': self.generate_basic_auth_headers,
        }
        self.username = self.config.get('username')
        self.password = self.config.get('password', '')
        self.http_method = self.config.get('http_method', 'POST')
        self.auth_method = self.config.get('auth_method', 'basic')
        if self.auth_method not in self.supported_auth_methods:
            raise ConfigError('HTTP Authentication method %s'
                              ' not supported' % (repr(self.auth_method,)))

    def generate_basic_auth_headers(self, username, password):
        credentials = ':'.join([username, password])
        auth_string = b64encode(credentials.encode('utf-8'))
        return {
            'Authorization': ['Basic %s' % (auth_string,)]
        }

    def get_auth_headers(self):
        if self.username is not None:
            handler = self.supported_auth_methods.get(self.auth_method)
            return handler(self.username, self.password)
        return {}

    @inlineCallbacks
    def setup_application(self):
        # start receipt web resource
        self.web_resource = yield self.start_web_resources(
            [
                (SendResource(self), self.web_path),
                (HealthResource(), 'health'),
            ],
            self.web_port)

    @inlineCallbacks
    def teardown_application(self):
        yield self.web_resource.loseConnection()

    def handle_raw_outbound_message(self, request):
        # TODO: handle username and password?
        # user=my_username
        # password=<PASSWORD>
        to_addr = request.args['id'][0]
        content = request.args['text'][0]
        return self.send_to(to_addr, content)

    def _call_rapidsms(self, message):
        headers = self.get_auth_headers()
        params = {
            'text': message['content'] or '',
            'id': message['from_addr'],
        }
        url = "%s?%s" % (self.rapidsms_url, urlencode(params))
        return http_request_full(url, headers=headers)

    def consume_user_message(self, message):
        return self._call_rapidsms(message)

    def close_session(self, message):
        return self._call_rapidsms(message)

    def consume_ack(self, event):<commit_msg>Log failures to contact RapidSMS rather than falling over (it does mean lost messages though if RapidSMS is down -- we need a better plan here.<commit_after># -*- test-case-name: vumi.application.tests.test_rapidsms_relay -*-
from urllib import urlencode
from base64 import b64encode

from twisted.internet.defer import inlineCallbacks
from twisted.web import http
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET

from vumi.application.base import ApplicationWorker
from vumi.utils import http_request_full
from vumi.errors import ConfigError
from vumi import log


class HealthResource(Resource):
    isLeaf = True

    def render_GET(self, request):
        request.setResponseCode(http.OK)
        request.do_not_log = True
        return 'OK'


class SendResource(Resource):
    isLeaf = True

    def __init__(self, application):
        self.application = application
        Resource.__init__(self)

    def finish_request(self, request, msg):
        request.setResponseCode(http.OK)
        request.write(msg.to_json())
        request.finish()

    def render_(self, request):
        log.msg("Send request: %s" % (request,))
        request.setHeader("content-type", "application/json")
        d = self.application.handle_raw_outbound_message(request)
        d.addCallback(lambda msg: self.finish_request(request, msg))
        return NOT_DONE_YET

    def render_PUT(self, request):
        return self.render_(request)

    def render_GET(self, request):
        return self.render_(request)

    def render_POST(self, request):
        return self.render_(request)


class RapidSMSRelay(ApplicationWorker):
    """Application that relays messages to RapidSMS.

    RapidSMS relay configuration options:

    :param str rapidsms_url:
        URL of the rapidsms http backend.
    :param str web_path:
        Path to listen for outbound messages from RapidSMS on.
    :param str web_port:
        Port to listen for outbound messages from RapidSMS on.
    :param str username:
        Username to use for the `rapidsms_url` (default: no authentication).
    :param str password:
        Password to use for the `rapidsms_url` (default: none).
    :param str auth_method:
        Authentication method to use with `rapidsms_url` (default: 'basic').
        The 'basic' method is currently the only available method.
    :param str http_method:
        HTTP request method to use for the `rapidsms_url` (default: POST)

    A RapidSMS relay requires a `send_to` configuration section for the
    `default` send_to tag.
    """

    SEND_TO_TAGS = frozenset(['default'])

    def validate_config(self):
        self.rapidsms_url = self.config['rapidsms_url']
        self.web_path = self.config['web_path']
        self.web_port = int(self.config['web_port'])

        self.supported_auth_methods = {
            'basic': self.generate_basic_auth_headers,
        }
        self.username = self.config.get('username')
        self.password = self.config.get('password', '')
        self.http_method = self.config.get('http_method', 'POST')
        self.auth_method = self.config.get('auth_method', 'basic')
        if self.auth_method not in self.supported_auth_methods:
            raise ConfigError('HTTP Authentication method %s'
                              ' not supported' % (repr(self.auth_method,)))

    def generate_basic_auth_headers(self, username, password):
        credentials = ':'.join([username, password])
        auth_string = b64encode(credentials.encode('utf-8'))
        return {
            'Authorization': ['Basic %s' % (auth_string,)]
        }

    def get_auth_headers(self):
        if self.username is not None:
            handler = self.supported_auth_methods.get(self.auth_method)
            return handler(self.username, self.password)
        return {}

    @inlineCallbacks
    def setup_application(self):
        # start receipt web resource
        self.web_resource = yield self.start_web_resources(
            [
                (SendResource(self), self.web_path),
                (HealthResource(), 'health'),
            ],
            self.web_port)

    @inlineCallbacks
    def teardown_application(self):
        yield self.web_resource.loseConnection()

    def handle_raw_outbound_message(self, request):
        # TODO: handle username and password?
        # user=my_username
        # password=<PASSWORD>
        to_addr = request.args['id'][0]
        content = request.args['text'][0]
        return self.send_to(to_addr, content)

    def _call_rapidsms(self, message):
        headers = self.get_auth_headers()
        params = {
            'text': message['content'] or '',
            'id': message['from_addr'],
        }
        url = "%s?%s" % (self.rapidsms_url, urlencode(params))
        d = http_request_full(url, headers=headers)
        d.addCallback(lambda response: log.info(response.code))
        d.addErrback(lambda failure: log.err(failure))
        return d

    def consume_user_message(self, message):
        return self._call_rapidsms(message)

    def close_session(self, message):
        return self._call_rapidsms(message)

    def consume_ack(self, event):
{'fraction_non_alphanumeric': 0.09758024691358025, 'fraction_numerical': 0.0017777777777777779, 'mean_word_length': 2.83996966249526, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4804716', 'n_tokens_mistral': 2931, 'n_tokens_neox': 2854, 'n_words': 729}