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
--------------
- Jalur web juga bisa multi modul
......
......@@ -7,14 +7,14 @@
keys = root, iso8583_web
[handlers]
keys = console
keys = console, file
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
handlers = console, file
[logger_iso8583_web]
level = DEBUG
......@@ -27,34 +27,29 @@ args = (sys.stderr,)
level = NOTSET
formatter = generic
[handler_file]
class = FileHandler
args = ('/home/sugiana/log/bank.log', 'a')
level = DEBUG
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)s %(message)s
# Aktifkan web server jika ingin simulasi sebagai bank dimana inquiry dkk
# 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]
[host_pemda]
ip = 127.0.0.1
port = 10002
listen = false
listen = true
streamer = bjb_with_suffix
[host_mitracomm]
ip = 127.0.0.1
port = 8583
streamer = mitracomm
active = false
timeout = 60
module = opensipkd.iso8583.bjb.pbb.test
# Aktifkan web server dimana inquiry dkk bisa dilakukan melalui web client.
[web]
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()
allowed_ips = list()
web = dict()
web_ip_conf = dict()
web_path_conf = dict()
def get_conf(ip, port):
......@@ -40,6 +40,13 @@ def get_int(conf, section, option, 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.'
......@@ -74,8 +81,8 @@ def append_others(cfg, conf, section):
cfg[key] = val
def load_module(cfg, conf, section, default):
cfg['module'] = get_str(conf, section, 'module', default)
def load_module(cfg, conf, section):
cfg['module'] = conf.get(section, 'module')
cfg['module_obj'] = get_module_object(cfg['module'])
module_section = 'module_' + cfg['module']
if conf.has_section(module_section):
......@@ -92,14 +99,19 @@ def read_web_conf(conf, section):
web['port'] = conf.getint(section, 'port')
web['threads'] = conf.getint(section, 'threads')
return
if section.find('web_host_') != 0:
if section.find('web_') != 0:
return
cfg = dict()
cfg['name'] = section.split('_')[-1]
cfg['ip'] = conf.get(section, 'ip')
cfg['host'] = conf.get(section, 'host')
load_module(cfg, conf, section, 'iso8583_web.scripts.views.jsonrpc')
web_ip_conf[cfg['ip']] = dict(cfg)
cfg['name'] = section.split('web_')[-1]
cfg['allowed_ip'] = get_list(conf, section, 'allowed_ip', [])
append_others(cfg, conf, section)
cfg['module_obj'] = get_module_object(cfg['module'])
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):
......@@ -120,7 +132,7 @@ def read_host_conf(conf, section):
cfg['echo'] = get_boolean(conf, section, 'echo', not cfg['listen'])
cfg['timeout'] = get_int(conf, section, 'timeout', 60)
append_others(cfg, conf, section)
load_module(cfg, conf, section, 'opensipkd.iso8583.network')
load_module(cfg, conf, section)
if cfg['listen']:
if cfg['port'] not in listen_ports:
listen_ports.append(cfg['port'])
......
......@@ -20,7 +20,7 @@ from ..read_conf import (
read_conf,
ip_conf,
web as web_conf,
web_ip_conf,
web_path_conf,
listen_ports,
allowed_ips,
get_conf,
......@@ -347,7 +347,7 @@ def start_web_server():
host = '0.0.0.0'
with Configurator() as config:
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)
config.scan(cfg['module'])
config.scan(__name__)
......
......@@ -10,5 +10,5 @@ def main(argv=sys.argv):
conf.read(conf_file)
cf = conf['module_iso8583_web.scripts.views.linkaja']
engine = create_engine(cf['db_url'])
engine.echot = True
engine.echo = True
Base.metadata.create_all(engine)
......@@ -5,7 +5,7 @@ from time import (
from opensipkd.tcp.connection import join_ip_port
from iso8583_web.read_conf import (
name_conf,
web_ip_conf,
web_path_conf,
)
from iso8583_web.scripts.logger import (
log_web_info,
......@@ -122,13 +122,20 @@ class View(object):
return found_conn
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):
web_conf = self.get_web_conf()
name = web_conf['host']
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):
msg = 'Host {} tidak ditemukan di konfigurasi'.format(hostname)
return Exception(msg)
......
......@@ -12,6 +12,7 @@ from . import (
)
ROUTE = 'rpc'
conf = dict()
......@@ -50,20 +51,24 @@ class View(BaseView):
def log_send(self, p):
BaseView.log_send(self, p)
@jsonrpc_method(endpoint='rpc')
@jsonrpc_method(endpoint=ROUTE)
def echo(self, p):
self.validate()
return self.get_response('echo', 'echo_request')
@jsonrpc_method(endpoint='rpc')
@jsonrpc_method(endpoint=ROUTE)
def inquiry(self, p):
self.validate()
return self.get_response('inquiry', p)
@jsonrpc_method(endpoint='rpc')
@jsonrpc_method(endpoint=ROUTE)
def payment(self, p):
self.validate()
return self.get_response('payment', p)
@jsonrpc_method(endpoint='rpc')
@jsonrpc_method(endpoint=ROUTE)
def reversal(self, p):
self.validate()
return self.get_response('reversal', p)
......@@ -75,4 +80,5 @@ def init(cfg):
# Dipanggil forwarder.py
def pyramid_init(config):
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 (
view_config,
notfound_view_config,
)
from pyramid.response import Response
from deform import (
Form,
Button,
......@@ -23,6 +24,11 @@ from opensipkd.string import (
)
from opensipkd.iso8583.bjb.pbb.structure import INVOICE_PROFILE
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 (
WebJob as BaseWebJob,
View as BaseView,
......@@ -38,6 +44,7 @@ from .exceptions import (
BaseError,
AmountError,
BillRefNotFound,
PaymentNotFound,
)
from .structure import (
DataRequest,
......@@ -51,7 +58,7 @@ from .models import (
)
ROUTE = 'rpc'
ROUTE = 'linkaja'
RENDERER = 'csv'
METHOD = {
'021': 'inquiry',
......@@ -104,7 +111,7 @@ def get_payment(data):
DBSession = get_db_session()
bill_ref = int(data['bill_ref'])
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())
return q.first()
......@@ -149,7 +156,6 @@ class csv_method(object):
kw.setdefault('attr', wrapped.__name__)
kw['_info'] = info.codeinfo # fbo action_method
print('DEBUG wrapped {}'.format(wrapped))
return wrapped
......@@ -237,7 +243,10 @@ class View(BaseView):
elif iso_data[39] in ['33', '55']:
err = InvoiceIdError()
elif iso_data[39] == '54':
err = AlreadyPaidError()
if is_reversal(data):
err = PaymentNotFound()
else:
err = AlreadyPaidError()
elif iso_data[39] == '51':
err = AmountError()
else:
......@@ -278,6 +287,7 @@ class View(BaseView):
@csv_method(route_name=ROUTE)
def view_trx(self):
self.validate()
if not self.request.POST:
self.log_receive('GET {}'.format(self.request.GET))
raise NeedPostError()
......@@ -294,6 +304,7 @@ class View(BaseView):
break
raise InternalError(msg)
data = dict(c)
return self.get_response(data)
try:
r = self.get_response(data)
self.log_send(r)
......@@ -310,6 +321,27 @@ class View(BaseView):
self.log_receive(msg, True)
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
def init(cfg):
conf.update(cfg)
......@@ -319,6 +351,7 @@ def init(cfg):
def pyramid_init(config):
config.add_renderer(RENDERER, 'iso8583_web.scripts.views.linkaja.Renderer')
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))
max_overflow = int(conf.get('db_max_overflow', 100))
engine = create_engine(
......
......@@ -50,3 +50,8 @@ class AmountError(BaseError):
class BillRefNotFound(BaseError):
code = '47'
message = 'bill_ref tidak ditemukan'
class PaymentNotFound(BaseError):
code = '48'
message = 'Belum ada pembayaran'
......@@ -44,6 +44,9 @@ class InquiryResponse(Row):
Row.__setitem__(self, name, value)
def __str__(self):
return self.to_str()
def to_str(self):
return ':'.join(list(self))
def from_err(self, err):
......
......@@ -68,13 +68,23 @@ def send(p):
try:
resp = requests.post(url, data=json.dumps(p), headers=headers)
durations[key] = time() - start
json_resp = resp.json()
log_info('Response: {}'.format(json_resp))
json_responses[key] = json_resp
if resp.status_code == 200:
json_resp = resp.json()
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:
durations[key] = time() - start
log_info('Response: {}'.format(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:
end_threads.append(key)
......@@ -130,7 +140,7 @@ def show_durations():
key_slowest = key
total_duration += duration
log_info(msg)
if key_fastest:
if key_fastest != key_slowest:
log_info('Tercepat {}'.format(messages[key_fastest]))
log_info('Terlama {}'.format(messages[key_slowest]))
log_info('Rerata {} detik / request'.format(total_duration/len(durations)))
......
......@@ -146,7 +146,7 @@ def show_durations():
else:
errors[err] = 1
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('Terlama {}'.format(messages[tid_slowest]))
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!