web_client_linkaja.py 7.33 KB
import sys
import os
import requests
from datetime import datetime
from time import (
    sleep,
    time,
    )
from threading import Thread
from argparse import ArgumentParser
from .views.linkaja.structure import (
    InquiryResponse,
    PaymentResponse,
    )


headers = {'content-type': 'application/x-www-form-urlencoded'}
threads = dict() 
end_threads = list() 
durations = dict() 
csv_responses = dict() 
server_info = dict()

default_url = 'http://localhost:7000/linkaja'
default_count = 1
default_merchant = 'ldmjakarta1'
default_terminal = 'Terminal Name'
default_pwd = 'ldmjkt1pass'
default_msisdn = '628111234567'

help_url = 'default ' + default_url
help_count = 'default {}'.format(default_count)
help_amount = 'wajib saat --payment dan --reversal'
help_bill_ref = 'wajib saat payment dan reversal, '\
    'diperoleh dari inquiry response'
help_trx_id = 'Nomor Transaksi Bank, '\
    'opsional saat --payment, wajib saat --reversal'
help_merchant = 'default {}'.format(default_merchant)


ERRORS = [
    'Connection refused',
    ]


def error(s):
    print('ERROR: {}'.format(s))
    sys.exit()


def log_info(s):
    t = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
    t = t[:-3]
    msg = '{} {}'.format(t, s)
    print(msg)


def get_option(argv):
    parser = ArgumentParser()
    parser.add_argument('--url', default=default_url, help=help_url)
    parser.add_argument(
        '--count', type=int, default=default_count, help=help_count)
    parser.add_argument('--invoice-id', required=True)
    parser.add_argument('--payment', action='store_true')
    parser.add_argument('--reversal', action='store_true')
    parser.add_argument('--amount', type=int, help=help_amount)
    parser.add_argument('--bill-ref', help=help_bill_ref)
    parser.add_argument('--trx-id', help=help_trx_id)
    parser.add_argument(
        '--merchant', default=default_merchant, help=help_merchant)
    parser.add_argument('--terminal', default=default_terminal)
    parser.add_argument('--pwd', default=default_pwd) 
    parser.add_argument('--msisdn', default=default_msisdn)
    parser.add_argument('--msg', default='')
    return parser.parse_args(argv)


def send(thread_id, p):
    url = server_info['url']
    log_info('Request: {}'.format(p))
    start = time()
    try:
        resp = requests.post(url, data=p, headers=headers, timeout=10)
        durations[thread_id] = time() - start
        data = p['trx_type'] == '021' and InquiryResponse() or \
            PaymentResponse()
        if resp.status_code == 200:
           data.from_raw(resp.text)
        log_info('Response {}: {} -> {}'.format(
            resp.status_code, [resp.text], data.values))
        csv_responses[thread_id] = resp
    except requests.exceptions.ConnectionError as e:
        durations[thread_id] = time() - start
        log_info('Response: {}'.format(e))
        csv_responses[thread_id] = dict(fatal=e)
    except requests.exceptions.ReadTimeout as e:
        durations[thread_id] = time() - start
        log_info('Response: {}'.format(e))
        csv_responses[thread_id] = dict(fatal=e)
    finally:
        end_threads.append(thread_id)


def show_errors(errors):
    if errors:
        for err, count in errors.items():
            log_info('{} {}'.format(err, count))
    else:
        log_info('Tidak ada yang gagal')


def nice_error(s):
    for msg in ERRORS:
        if s.find(msg) > -1:
            return msg
    return s


def show_durations():
    tid_fastest = tid_slowest = None
    total_duration = 0
    messages = dict()
    errors = dict()
    for tid in durations:
        duration = durations[tid]
        resp = csv_responses.get(tid)
        if resp:
            err = None
            if 'fatal' in resp:
                err = msg = nice_error(str(resp['fatal']))
            elif resp.status_code == 200:
                messages[tid] = msg = resp.text.strip()
                if tid_fastest:
                    if duration < durations[tid_fastest]:
                        tid_fastest = tid 
                else:
                    tid_fastest = tid
                if tid_slowest:
                    if duration > durations[tid_slowest]:
                        tid_slowest = tid 
                else:
                    tid_slowest = tid 
                total_duration += duration
            else:
                err = msg = resp.text.split('\n')[0].strip()
        else:
            err = msg = 'KOSONG'
        if err:
            if err in errors:
                errors[err] += 1
            else:
                errors[err] = 1
        log_info('thread {} {} detik {}'.format(tid, duration, msg))
    if tid_fastest != tid_slowest:
        log_info('Tercepat {}'.format(messages[tid_fastest]))
        log_info('Terlama {}'.format(messages[tid_slowest]))
        log_info('Rerata {} detik / request'.format(total_duration/len(durations)))
    show_errors(errors)


class App:
    def __init__(self, argv):
        self.option = get_option(argv)
        server_info['url'] = self.option.url

    def create_thread(self, thread_id, data):
        thread = Thread(target=send, args=[thread_id, data])
        # Exit the server thread when the main thread terminates
        thread.daemon = True
        threads[thread_id] = thread
        thread.start()

    def get_invoice_ids(self):
        if not os.path.exists(self.option.invoice_id):
            return [self.option.invoice_id]
        r = []
        with open(self.option.invoice_id) as f:
            for line in f.readlines():
                invoice_id = line.rstrip()
                r += [invoice_id]
        return r

    def get_method(self):
        if self.option.payment:
            return '022'
        if self.option.reversal:
            return '023'
        return '021'

    def get_transaction(self, invoice_id):
        def required(name, default=None):
            value = getattr(self.option, name)
            if not value and not default:
                error('--{} harus diisi'.format(name.replace('_', '-')))
            p[name] = value or default

        p = dict(
                merchant=self.option.merchant,
                terminal=self.option.terminal,
                pwd=self.option.pwd,
                msisdn=self.option.msisdn,
                acc_no=self.option.invoice_id,
                trx_date=datetime.now().strftime('%Y%m%d%H%M%S'),
                msg=self.option.msg)
        p['trx_type'] = self.get_method()
        if self.option.payment or self.option.reversal:
            required('amount')
            required('bill_ref')
            if self.option.payment:
                required('trx_id', datetime.now().strftime('%m%d%H%M%S'))
            else:
                required('trx_id')
        return p

    def run_transaction(self):
        thread_id = 0
        for i in range(self.option.count):
            for invoice_id in self.get_invoice_ids():
                thread_id += 1
                data = self.get_transaction(invoice_id)
                self.create_thread(thread_id, data)

    def run(self):
        self.run_transaction()
        while threads:
            if not end_threads:
                continue
            i = end_threads[0]
            if i in threads:
                thread = threads[i]
                thread.join()
                del threads[i]
            index = end_threads.index(i)
            del end_threads[index]
        show_durations()


def main(argv=sys.argv[1:]):
    app = App(argv)
    app.run()