Commit a1c645b7 by Owo Sugiana

Konfigurasi web yang lebih ringkas

1 parent cbbddf07
0.2.1 2020-07-19
----------------
- Konfigurasi web yang lebih ringkas
- LinkAja RC 48 saat payment tidak ditemukan
- Contoh konfigurasi untuk simulasi sebagai bank
- Dokumentasi yang lebih baik dan fokus pada transaksi
0.2 2020-06-30 0.2 2020-06-30
-------------- --------------
- Jalur web juga bisa multi modul - Jalur web juga bisa multi modul
......
...@@ -7,14 +7,14 @@ ...@@ -7,14 +7,14 @@
keys = root, iso8583_web keys = root, iso8583_web
[handlers] [handlers]
keys = console keys = console, file
[formatters] [formatters]
keys = generic keys = generic
[logger_root] [logger_root]
level = INFO level = INFO
handlers = console handlers = console, file
[logger_iso8583_web] [logger_iso8583_web]
level = DEBUG level = DEBUG
...@@ -27,34 +27,29 @@ args = (sys.stderr,) ...@@ -27,34 +27,29 @@ args = (sys.stderr,)
level = NOTSET level = NOTSET
formatter = generic formatter = generic
[handler_file]
class = FileHandler
args = ('/home/sugiana/log/bank.log', 'a')
level = DEBUG
formatter = generic
[formatter_generic] [formatter_generic]
format = %(asctime)s %(levelname)s %(message)s format = %(asctime)s %(levelname)s %(message)s
# Aktifkan web server jika ingin simulasi sebagai bank dimana inquiry dkk [host_pemda]
# bisa dilakukan melalui web client.
# [web]
# port = 7000
# threads = 12
#[web_host_linkaja]
#ip = 127.0.0.1
#module = iso8583_web.scripts.views.linkaja
#host = pemda
#[module_iso8583_web.scripts.views.linkaja]
#route_path = /linkaja
#db_url = postgresql://sugiana:a@localhost/agratek
[host_bjb]
ip = 127.0.0.1 ip = 127.0.0.1
port = 10002 port = 10002
listen = false listen = true
streamer = bjb_with_suffix streamer = bjb_with_suffix
timeout = 60
[host_mitracomm] module = opensipkd.iso8583.bjb.pbb.test
ip = 127.0.0.1
port = 8583 # Aktifkan web server dimana inquiry dkk bisa dilakukan melalui web client.
streamer = mitracomm [web]
active = false port = 7000
threads = 12
[web_rpc]
route_path = /rpc
host = pemda
module = iso8583_web.scripts.views.jsonrpc
###
# logging configuration
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
keys = root, iso8583_web
[handlers]
keys = console, file
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console, file
[logger_iso8583_web]
level = DEBUG
handlers =
qualname = iso8583_web
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[handler_file]
class = FileHandler
args = ('/home/sugiana/log/pemda.log', 'a')
level = DEBUG
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)s %(message)s
[module_opensipkd.iso8583.bjb.pbb.bogor_kota]
db_url = postgresql://user:pass@localhost/db
db_pool_size = 50
db_max_overflow = 100
persen_denda = 2
nip_pencatat = 999999999
# Tempat pembayaran
kd_kanwil = 01
kd_kantor = 01
# Penerjemahan nilai bit NN menjadi kd_tp
# Prioritas sesuai urutan
kd_tp =
bit42:TOKOPEDIA:46
bit42:BUKALAPAK:47
bit43:INDOMARET:48
bit43:ALFAMART:49
bit18:6010:69
bit18:6011:71
bit18:6012:72
bit18:6013:73
bit18:6014:74
bit18:6015:75
bit18:6016:76
bit18:6017:77
bit0:default:20
[host_bjb]
ip = 127.0.0.1
port = 10002
listen = false
streamer = bjb_with_suffix
timeout = 60
module = opensipkd.iso8583.bjb.pbb.bogor_kota
[host_mitracomm]
ip = 127.0.0.1
port = 8583
listen = true
streamer = mitracomm
timeout = 60
module = opensipkd.iso8583.bjb.pbb.bogor_kota
...@@ -11,7 +11,7 @@ name_conf = dict() ...@@ -11,7 +11,7 @@ name_conf = dict()
allowed_ips = list() allowed_ips = list()
web = dict() web = dict()
web_ip_conf = dict() web_path_conf = dict()
def get_conf(ip, port): def get_conf(ip, port):
...@@ -40,6 +40,13 @@ def get_int(conf, section, option, default): ...@@ -40,6 +40,13 @@ def get_int(conf, section, option, default):
return default return default
def get_list(conf, section, option, default):
s = get_str(conf, section, option, '')
if s:
return s.split()
return default
MSG_DUPLICATE = 'IP {ip} port {port} ganda. Perbaiki konfigurasi.' MSG_DUPLICATE = 'IP {ip} port {port} ganda. Perbaiki konfigurasi.'
...@@ -74,8 +81,8 @@ def append_others(cfg, conf, section): ...@@ -74,8 +81,8 @@ def append_others(cfg, conf, section):
cfg[key] = val cfg[key] = val
def load_module(cfg, conf, section, default): def load_module(cfg, conf, section):
cfg['module'] = get_str(conf, section, 'module', default) cfg['module'] = conf.get(section, 'module')
cfg['module_obj'] = get_module_object(cfg['module']) cfg['module_obj'] = get_module_object(cfg['module'])
module_section = 'module_' + cfg['module'] module_section = 'module_' + cfg['module']
if conf.has_section(module_section): if conf.has_section(module_section):
...@@ -92,14 +99,19 @@ def read_web_conf(conf, section): ...@@ -92,14 +99,19 @@ def read_web_conf(conf, section):
web['port'] = conf.getint(section, 'port') web['port'] = conf.getint(section, 'port')
web['threads'] = conf.getint(section, 'threads') web['threads'] = conf.getint(section, 'threads')
return return
if section.find('web_host_') != 0: if section.find('web_') != 0:
return return
cfg = dict() cfg = dict()
cfg['name'] = section.split('_')[-1] cfg['name'] = section.split('web_')[-1]
cfg['ip'] = conf.get(section, 'ip') cfg['allowed_ip'] = get_list(conf, section, 'allowed_ip', [])
cfg['host'] = conf.get(section, 'host') append_others(cfg, conf, section)
load_module(cfg, conf, section, 'iso8583_web.scripts.views.jsonrpc') cfg['module_obj'] = get_module_object(cfg['module'])
web_ip_conf[cfg['ip']] = dict(cfg) try:
f_init = getattr(cfg['module_obj'], 'init')
f_init(cfg)
except AttributeError:
pass
web_path_conf[cfg['route_path']] = dict(cfg)
def read_host_conf(conf, section): def read_host_conf(conf, section):
...@@ -120,7 +132,7 @@ def read_host_conf(conf, section): ...@@ -120,7 +132,7 @@ def read_host_conf(conf, section):
cfg['echo'] = get_boolean(conf, section, 'echo', not cfg['listen']) cfg['echo'] = get_boolean(conf, section, 'echo', not cfg['listen'])
cfg['timeout'] = get_int(conf, section, 'timeout', 60) cfg['timeout'] = get_int(conf, section, 'timeout', 60)
append_others(cfg, conf, section) append_others(cfg, conf, section)
load_module(cfg, conf, section, 'opensipkd.iso8583.network') load_module(cfg, conf, section)
if cfg['listen']: if cfg['listen']:
if cfg['port'] not in listen_ports: if cfg['port'] not in listen_ports:
listen_ports.append(cfg['port']) listen_ports.append(cfg['port'])
......
...@@ -20,7 +20,7 @@ from ..read_conf import ( ...@@ -20,7 +20,7 @@ from ..read_conf import (
read_conf, read_conf,
ip_conf, ip_conf,
web as web_conf, web as web_conf,
web_ip_conf, web_path_conf,
listen_ports, listen_ports,
allowed_ips, allowed_ips,
get_conf, get_conf,
...@@ -347,7 +347,7 @@ def start_web_server(): ...@@ -347,7 +347,7 @@ def start_web_server():
host = '0.0.0.0' host = '0.0.0.0'
with Configurator() as config: with Configurator() as config:
config.include('pyramid_tm') config.include('pyramid_tm')
for ip, cfg in web_ip_conf.items(): for path, cfg in web_path_conf.items():
cfg['module_obj'].pyramid_init(config) cfg['module_obj'].pyramid_init(config)
config.scan(cfg['module']) config.scan(cfg['module'])
config.scan(__name__) config.scan(__name__)
......
...@@ -10,5 +10,5 @@ def main(argv=sys.argv): ...@@ -10,5 +10,5 @@ def main(argv=sys.argv):
conf.read(conf_file) conf.read(conf_file)
cf = conf['module_iso8583_web.scripts.views.linkaja'] cf = conf['module_iso8583_web.scripts.views.linkaja']
engine = create_engine(cf['db_url']) engine = create_engine(cf['db_url'])
engine.echot = True engine.echo = True
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
...@@ -5,7 +5,7 @@ from time import ( ...@@ -5,7 +5,7 @@ from time import (
from opensipkd.tcp.connection import join_ip_port from opensipkd.tcp.connection import join_ip_port
from iso8583_web.read_conf import ( from iso8583_web.read_conf import (
name_conf, name_conf,
web_ip_conf, web_path_conf,
) )
from iso8583_web.scripts.logger import ( from iso8583_web.scripts.logger import (
log_web_info, log_web_info,
...@@ -122,13 +122,20 @@ class View(object): ...@@ -122,13 +122,20 @@ class View(object):
return found_conn return found_conn
def get_web_conf(self): def get_web_conf(self):
return web_ip_conf.get(self.request.client_addr) return web_path_conf.get(self.request.path)
def get_iso_conf(self): def get_iso_conf(self):
web_conf = self.get_web_conf() web_conf = self.get_web_conf()
name = web_conf['host'] name = web_conf['host']
return name_conf[name] return name_conf[name]
def validate(self):
conf = self.get_web_conf()
if not conf['allowed_ip']:
return
if self.request.client_addr not in conf['allowed_ip']:
raise self.not_found_error(self.request.client_addr)
def not_found_error(self, hostname): def not_found_error(self, hostname):
msg = 'Host {} tidak ditemukan di konfigurasi'.format(hostname) msg = 'Host {} tidak ditemukan di konfigurasi'.format(hostname)
return Exception(msg) return Exception(msg)
......
...@@ -12,6 +12,7 @@ from . import ( ...@@ -12,6 +12,7 @@ from . import (
) )
ROUTE = 'rpc'
conf = dict() conf = dict()
...@@ -50,20 +51,24 @@ class View(BaseView): ...@@ -50,20 +51,24 @@ class View(BaseView):
def log_send(self, p): def log_send(self, p):
BaseView.log_send(self, p) BaseView.log_send(self, p)
@jsonrpc_method(endpoint='rpc') @jsonrpc_method(endpoint=ROUTE)
def echo(self, p): def echo(self, p):
self.validate()
return self.get_response('echo', 'echo_request') return self.get_response('echo', 'echo_request')
@jsonrpc_method(endpoint='rpc') @jsonrpc_method(endpoint=ROUTE)
def inquiry(self, p): def inquiry(self, p):
self.validate()
return self.get_response('inquiry', p) return self.get_response('inquiry', p)
@jsonrpc_method(endpoint='rpc') @jsonrpc_method(endpoint=ROUTE)
def payment(self, p): def payment(self, p):
self.validate()
return self.get_response('payment', p) return self.get_response('payment', p)
@jsonrpc_method(endpoint='rpc') @jsonrpc_method(endpoint=ROUTE)
def reversal(self, p): def reversal(self, p):
self.validate()
return self.get_response('reversal', p) return self.get_response('reversal', p)
...@@ -75,4 +80,5 @@ def init(cfg): ...@@ -75,4 +80,5 @@ def init(cfg):
# Dipanggil forwarder.py # Dipanggil forwarder.py
def pyramid_init(config): def pyramid_init(config):
config.include('pyramid_rpc.jsonrpc') config.include('pyramid_rpc.jsonrpc')
config.add_jsonrpc_endpoint('rpc', conf['route_path']) config.add_jsonrpc_endpoint(ROUTE, conf['route_path'])
log_web_info('JSON-RPC route path {}'.format(conf['route_path']))
...@@ -11,6 +11,7 @@ from pyramid.view import ( ...@@ -11,6 +11,7 @@ from pyramid.view import (
view_config, view_config,
notfound_view_config, notfound_view_config,
) )
from pyramid.response import Response
from deform import ( from deform import (
Form, Form,
Button, Button,
...@@ -23,6 +24,11 @@ from opensipkd.string import ( ...@@ -23,6 +24,11 @@ from opensipkd.string import (
) )
from opensipkd.iso8583.bjb.pbb.structure import INVOICE_PROFILE from opensipkd.iso8583.bjb.pbb.structure import INVOICE_PROFILE
from iso8583_web.scripts.tools import iso_to_dict from iso8583_web.scripts.tools import iso_to_dict
from iso8583_web.read_conf import web_path_conf
from iso8583_web.scripts.logger import (
log_web_info,
log_web_error,
)
from .. import ( from .. import (
WebJob as BaseWebJob, WebJob as BaseWebJob,
View as BaseView, View as BaseView,
...@@ -38,6 +44,7 @@ from .exceptions import ( ...@@ -38,6 +44,7 @@ from .exceptions import (
BaseError, BaseError,
AmountError, AmountError,
BillRefNotFound, BillRefNotFound,
PaymentNotFound,
) )
from .structure import ( from .structure import (
DataRequest, DataRequest,
...@@ -51,7 +58,7 @@ from .models import ( ...@@ -51,7 +58,7 @@ from .models import (
) )
ROUTE = 'rpc' ROUTE = 'linkaja'
RENDERER = 'csv' RENDERER = 'csv'
METHOD = { METHOD = {
'021': 'inquiry', '021': 'inquiry',
...@@ -104,7 +111,7 @@ def get_payment(data): ...@@ -104,7 +111,7 @@ def get_payment(data):
DBSession = get_db_session() DBSession = get_db_session()
bill_ref = int(data['bill_ref']) bill_ref = int(data['bill_ref'])
q = DBSession.query(Rpc).filter_by( q = DBSession.query(Rpc).filter_by(
inquiry_id=bill_ref, trx_type=data['trx_type']) inquiry_id=bill_ref, trx_type='022')
q = q.order_by(Rpc.id.desc()) q = q.order_by(Rpc.id.desc())
return q.first() return q.first()
...@@ -149,7 +156,6 @@ class csv_method(object): ...@@ -149,7 +156,6 @@ class csv_method(object):
kw.setdefault('attr', wrapped.__name__) kw.setdefault('attr', wrapped.__name__)
kw['_info'] = info.codeinfo # fbo action_method kw['_info'] = info.codeinfo # fbo action_method
print('DEBUG wrapped {}'.format(wrapped))
return wrapped return wrapped
...@@ -237,7 +243,10 @@ class View(BaseView): ...@@ -237,7 +243,10 @@ class View(BaseView):
elif iso_data[39] in ['33', '55']: elif iso_data[39] in ['33', '55']:
err = InvoiceIdError() err = InvoiceIdError()
elif iso_data[39] == '54': elif iso_data[39] == '54':
err = AlreadyPaidError() if is_reversal(data):
err = PaymentNotFound()
else:
err = AlreadyPaidError()
elif iso_data[39] == '51': elif iso_data[39] == '51':
err = AmountError() err = AmountError()
else: else:
...@@ -278,6 +287,7 @@ class View(BaseView): ...@@ -278,6 +287,7 @@ class View(BaseView):
@csv_method(route_name=ROUTE) @csv_method(route_name=ROUTE)
def view_trx(self): def view_trx(self):
self.validate()
if not self.request.POST: if not self.request.POST:
self.log_receive('GET {}'.format(self.request.GET)) self.log_receive('GET {}'.format(self.request.GET))
raise NeedPostError() raise NeedPostError()
...@@ -294,6 +304,7 @@ class View(BaseView): ...@@ -294,6 +304,7 @@ class View(BaseView):
break break
raise InternalError(msg) raise InternalError(msg)
data = dict(c) data = dict(c)
return self.get_response(data)
try: try:
r = self.get_response(data) r = self.get_response(data)
self.log_send(r) self.log_send(r)
...@@ -310,6 +321,27 @@ class View(BaseView): ...@@ -310,6 +321,27 @@ class View(BaseView):
self.log_receive(msg, True) self.log_receive(msg, True)
return self.request.exception return self.request.exception
def log_prefix(request):
web_conf = web_path_conf.get(request.path)
name = web_conf['name']
return '{} {} {}'.format(
request.client_addr, name, id(request))
@view_config(context=BaseError)
def view_exception(exc, request):
r = InquiryResponse()
r['Response Code'] = exc.code
r['Notification Message'] = exc.message
prefix = log_prefix(request)
msg = '{} {} {}'.format(prefix, 'Send', r)
log_web_error(msg)
response = Response(str(r))
response.status_int = 200
return response
# Dipanggil read_conf.py # Dipanggil read_conf.py
def init(cfg): def init(cfg):
conf.update(cfg) conf.update(cfg)
...@@ -319,6 +351,7 @@ def init(cfg): ...@@ -319,6 +351,7 @@ def init(cfg):
def pyramid_init(config): def pyramid_init(config):
config.add_renderer(RENDERER, 'iso8583_web.scripts.views.linkaja.Renderer') config.add_renderer(RENDERER, 'iso8583_web.scripts.views.linkaja.Renderer')
config.add_route(ROUTE, conf['route_path']) config.add_route(ROUTE, conf['route_path'])
log_web_info('LinkAja route path {}'.format(conf['route_path']))
pool_size = int(conf.get('db_pool_size', 50)) pool_size = int(conf.get('db_pool_size', 50))
max_overflow = int(conf.get('db_max_overflow', 100)) max_overflow = int(conf.get('db_max_overflow', 100))
engine = create_engine( engine = create_engine(
......
...@@ -50,3 +50,8 @@ class AmountError(BaseError): ...@@ -50,3 +50,8 @@ class AmountError(BaseError):
class BillRefNotFound(BaseError): class BillRefNotFound(BaseError):
code = '47' code = '47'
message = 'bill_ref tidak ditemukan' message = 'bill_ref tidak ditemukan'
class PaymentNotFound(BaseError):
code = '48'
message = 'Belum ada pembayaran'
...@@ -44,6 +44,9 @@ class InquiryResponse(Row): ...@@ -44,6 +44,9 @@ class InquiryResponse(Row):
Row.__setitem__(self, name, value) Row.__setitem__(self, name, value)
def __str__(self): def __str__(self):
return self.to_str()
def to_str(self):
return ':'.join(list(self)) return ':'.join(list(self))
def from_err(self, err): def from_err(self, err):
......
...@@ -68,13 +68,23 @@ def send(p): ...@@ -68,13 +68,23 @@ def send(p):
try: try:
resp = requests.post(url, data=json.dumps(p), headers=headers) resp = requests.post(url, data=json.dumps(p), headers=headers)
durations[key] = time() - start durations[key] = time() - start
json_resp = resp.json() if resp.status_code == 200:
log_info('Response: {}'.format(json_resp)) json_resp = resp.json()
json_responses[key] = json_resp log_info('Response: {}'.format(json_resp))
json_responses[key] = json_resp
else:
log_info('Status Code: {}'.format(resp.status_code))
log_info('Body: {}'.format([resp.text]))
json_responses[key] = dict(fatal=resp.text)
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
durations[key] = time() - start durations[key] = time() - start
log_info('Response: {}'.format(e)) log_info('Response: {}'.format(e))
json_responses[key] = dict(fatal=e) json_responses[key] = dict(fatal=e)
except json.decoder.JSONDecodeError as e:
durations[key] = time() - start
log_info('Body: {}'.format([resp.text]))
log_info('Response: {}'.format(e))
json_responses[key] = dict(fatal=e)
finally: finally:
end_threads.append(key) end_threads.append(key)
...@@ -130,7 +140,7 @@ def show_durations(): ...@@ -130,7 +140,7 @@ def show_durations():
key_slowest = key key_slowest = key
total_duration += duration total_duration += duration
log_info(msg) log_info(msg)
if key_fastest: if key_fastest != key_slowest:
log_info('Tercepat {}'.format(messages[key_fastest])) log_info('Tercepat {}'.format(messages[key_fastest]))
log_info('Terlama {}'.format(messages[key_slowest])) log_info('Terlama {}'.format(messages[key_slowest]))
log_info('Rerata {} detik / request'.format(total_duration/len(durations))) log_info('Rerata {} detik / request'.format(total_duration/len(durations)))
......
...@@ -146,7 +146,7 @@ def show_durations(): ...@@ -146,7 +146,7 @@ def show_durations():
else: else:
errors[err] = 1 errors[err] = 1
log_info('thread {} {} detik {}'.format(tid, duration, msg)) log_info('thread {} {} detik {}'.format(tid, duration, msg))
if tid_fastest: if tid_fastest != tid_slowest:
log_info('Tercepat {}'.format(messages[tid_fastest])) log_info('Tercepat {}'.format(messages[tid_fastest]))
log_info('Terlama {}'.format(messages[tid_slowest])) log_info('Terlama {}'.format(messages[tid_slowest]))
log_info('Rerata {} detik / request'.format(total_duration/len(durations))) log_info('Rerata {} detik / request'.format(total_duration/len(durations)))
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!