Commit db249bb3 by Owo Sugiana

Thread web service yang sebenarnya

1 parent 1f950e88
include *.txt *.ini *.rst include CHANGES.txt production.ini development.ini README.rst
recursive-include web_starter *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak recursive-include iso8583_web *.py *.png *.css *.pt *.tpl *.mako
Daemon ISO8583 Daemon ISO8583
============== ==============
Daemon ISO8583 ini bisa sebagai client maupun server. Berikut ini cara Daemon ISO8583 ini bisa sebagai client maupun server. Berikut ini cara
pemasangannya. Nama paketnya mengandung web karena ia bisa dihubungi melalui pemasangannya. Nama paketnya mengandung web karena ia bisa dihubungi melalui
web service JsonRPC untuk melakukan inquiry, payment, dan reversal. web service JsonRPC untuk melakukan inquiry, payment, dan reversal.
...@@ -21,11 +20,8 @@ Salinlah file konfigurasi:: ...@@ -21,11 +20,8 @@ Salinlah file konfigurasi::
$ cp iso8583.ini test-pemda.ini $ cp iso8583.ini test-pemda.ini
Sesuaikan isi section ``[host_bjb]``. Anda bisa menambahkan host lainnya baik Sesuaikan isi section ``[host_bjb]``. Anda bisa menambahkan host lainnya
bank, pemda, bahkan payment gateway dengan menambahkan awalan ``host_`` pada dengan menambahkan awalan ``host_`` pada section. Kemudian lakukan uji coba::
section.
Kemudian lakukan uji coba::
$ ../env/bin/pip install -e '.[testing]' $ ../env/bin/pip install -e '.[testing]'
$ ../env/bin/pytest iso8583_web/test-conf.py $ ../env/bin/pytest iso8583_web/test-conf.py
...@@ -48,9 +44,6 @@ Biarkan ini aktif. Buka konsol lain. Sekarang kita buat simulator BJB:: ...@@ -48,9 +44,6 @@ Biarkan ini aktif. Buka konsol lain. Sekarang kita buat simulator BJB::
Ubah file ``test-bjb.ini`` pada baris:: Ubah file ``test-bjb.ini`` pada baris::
[web]
port = 6543
[host_bjb] [host_bjb]
ip = 127.0.0.1 ip = 127.0.0.1
port = 10002 port = 10002
...@@ -59,9 +52,6 @@ Ubah file ``test-bjb.ini`` pada baris:: ...@@ -59,9 +52,6 @@ Ubah file ``test-bjb.ini`` pada baris::
menjadi:: menjadi::
[web]
port = 6544
[host_pemda] [host_pemda]
ip = 127.0.0.1 ip = 127.0.0.1
port = 10002 port = 10002
...@@ -71,3 +61,79 @@ menjadi:: ...@@ -71,3 +61,79 @@ menjadi::
Simpan, lalu jalankan:: Simpan, lalu jalankan::
../env/bin/iso8583 test-bjb.ini ../env/bin/iso8583 test-bjb.ini
Log File
--------
Untuk menyimpan log ke dalam file lakukan perubahan pada file konfigurasi (INI
file). Pada section ``[handlers]`` baris ``keys`` tambahkan ``file`` sehingga
menjadi::
[handlers]
keys = console, file
Pada section ``[logger_root]`` baris ``handlers`` tambahkan ``file`` sehingga
menjadi::
[logger_root]
level = INFO
handlers = console, file
Setelah section ``[handler_console]`` tambahkan section ``[handler_file]``
seperti ini::
[handler_file]
class = FileHandler
args = ('/home/sugiana/tmp/pemda.log', 'a')
level = NOTSET
formatter = generic
Lalu jalankan lagi daemon seperti di atas.
JsonRpc
-------
Ini adalah web service untuk memudahkan pembuatan client. Misalkan kita ingin
membuat aplikasi teller bank. Pada ``test-bjb.ini`` aktifkan section
``[web]``::
[web]
port = 7000
Kemudian restart daemon-nya. Setelah *echo established* dengan daemon pemda
lakukan *echo request* dengan cara::
$ ../env/bin/python contrib/bank-teller.py
Hasilnya menjadi seperti ini::
{'id': 0,
'jsonrpc': '2.0',
'result': {'code': 0,
'data': {'11': '163105',
'39': '00',
'7': '0221163106',
'70': '301'},
'message': 'OK'}}
JsonRpc Log File
----------------
Biasanya tahap awal pembuatan web client terjadi kesalahan yang tampak di
client namun tak nampak di log web server. Untuk mengatasi hal ini lakukan
penambahan di file konfigurasi section ``[loggers]`` menjadi seperti ini::
[loggers]
keys = root, iso8583_web, jsonrpc
Lalu tambahkan section ``[logger_jsonrpc]`` berikut ini::
[logger_jsonrpc]
level = DEBUG
handlers = console, file
qualname = pyramid_rpc.jsonrpc
Kemudian jalankan lagi daemon-nya. Misalkan client memanggil method ``eco``
yang seharusnya ``echo`` maka di log akan tampil seperti ini::
2019-02-21 14:00:47,135 DEBUG handling id:0 method:eco
2019-02-21 14:00:47,135 DEBUG json-rpc method not found rpc_id:0 "eco"
Semoga dipahami.
...@@ -64,6 +64,7 @@ def run_migrations_online(): ...@@ -64,6 +64,7 @@ def run_migrations_online():
with context.begin_transaction(): with context.begin_transaction():
context.run_migrations() context.run_migrations()
if context.is_offline_mode(): if context.is_offline_mode():
run_migrations_offline() run_migrations_offline()
else: else:
......
...@@ -17,13 +17,16 @@ depends_on = None ...@@ -17,13 +17,16 @@ depends_on = None
def upgrade(): def upgrade():
op.alter_column('users', 'last_login_date', op.alter_column(
'users', 'last_login_date',
type_=sa.DateTime(timezone=True), type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False)) existing_type=sa.DateTime(timezone=False))
op.alter_column('users', 'registered_date', op.alter_column(
'users', 'registered_date',
type_=sa.DateTime(timezone=True), type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False)) existing_type=sa.DateTime(timezone=False))
op.alter_column('users', 'security_code_date', op.alter_column(
'users', 'security_code_date',
type_=sa.DateTime(timezone=True), type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False)) existing_type=sa.DateTime(timezone=False))
......
import requests
import json
from pprint import pprint
url = 'http://localhost:7000/rpc'
headers = {'content-type': 'application/json'}
p = {'host': 'pemda'}
data = {
'method': 'echo',
'params': [p],
'jsonrpc': '2.0',
'id': 0,
}
resp = requests.post(url, data=json.dumps(data), headers=headers)
json_resp = resp.json()
pprint(json_resp)
...@@ -30,8 +30,10 @@ formatter = generic ...@@ -30,8 +30,10 @@ formatter = generic
[formatter_generic] [formatter_generic]
format = %(asctime)s %(levelname)s %(message)s format = %(asctime)s %(levelname)s %(message)s
[web] # Aktifkan JsonRPC server jika ingin simulasi sebagai bank dimana inquiry dkk
port = 6543 # bisa dilakukan melalui JsonRPC client.
# [web]
# port = 7000
[host_bjb] [host_bjb]
ip = 127.0.0.1 ip = 127.0.0.1
......
...@@ -13,6 +13,7 @@ except ImportError: ...@@ -13,6 +13,7 @@ except ImportError:
listen_ports = [] listen_ports = []
ip_conf = {} ip_conf = {}
name_conf = {}
allowed_ips = [] allowed_ips = []
web = {} web = {}
...@@ -23,7 +24,7 @@ def get_conf(ip, port): ...@@ -23,7 +24,7 @@ def get_conf(ip, port):
def get_web_port(): def get_web_port():
return web['port'] return 'port' in web and web['port']
def get_str(conf, section, option, default): def get_str(conf, section, option, default):
...@@ -94,7 +95,7 @@ def read_conf(conf_file): ...@@ -94,7 +95,7 @@ def read_conf(conf_file):
cfg = dict() cfg = dict()
cfg['ip'] = ip cfg['ip'] = ip
cfg['port'] = port cfg['port'] = port
cfg['name'] = section.split('_')[1] cfg['name'] = name = section.split('_')[1]
cfg['streamer'] = get_str(conf, section, 'streamer', 'none') cfg['streamer'] = get_str(conf, section, 'streamer', 'none')
cfg['streamer_cls'] = get_streamer_class(cfg['streamer']) cfg['streamer_cls'] = get_streamer_class(cfg['streamer'])
cfg['module'] = get_str(conf, section, 'module', 'opensipkd.iso8583.network') cfg['module'] = get_str(conf, section, 'module', 'opensipkd.iso8583.network')
...@@ -109,4 +110,4 @@ def read_conf(conf_file): ...@@ -109,4 +110,4 @@ def read_conf(conf_file):
listen_ports.append(port) listen_ports.append(port)
if ip not in allowed_ips: if ip not in allowed_ips:
allowed_ips.append(ip) allowed_ips.append(ip)
ip_conf[ip_port] = dict(cfg) ip_conf[ip_port] = name_conf[name] = dict(cfg)
...@@ -2,12 +2,15 @@ import os ...@@ -2,12 +2,15 @@ import os
import sys import sys
import logging import logging
import signal import signal
from time import sleep from time import (
sleep,
time,
)
from threading import Thread from threading import Thread
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
from pyramid.config import Configurator from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.paster import setup_logging from pyramid.paster import setup_logging
from pyramid_rpc.jsonrpc import jsonrpc_method
from opensipkd.tcp.connection import ( from opensipkd.tcp.connection import (
ConnectionManager as BaseConnectionManager, ConnectionManager as BaseConnectionManager,
join_ip_port, join_ip_port,
...@@ -21,6 +24,11 @@ from opensipkd.tcp.server import ( ...@@ -21,6 +24,11 @@ from opensipkd.tcp.server import (
RequestHandler as BaseRequestHandler, RequestHandler as BaseRequestHandler,
) )
from opensipkd.tcp.client import Client as BaseClient from opensipkd.tcp.client import Client as BaseClient
from opensipkd.jsonrpc.exc import (
JsonRpcInvalidParams,
JsonRpcBankNotFound,
JsonRpcBillerNetwork,
)
from ..read_conf import ( from ..read_conf import (
read_conf, read_conf,
ip_conf, ip_conf,
...@@ -28,6 +36,7 @@ from ..read_conf import ( ...@@ -28,6 +36,7 @@ from ..read_conf import (
listen_ports, listen_ports,
allowed_ips, allowed_ips,
get_conf, get_conf,
name_conf,
) )
...@@ -295,6 +304,15 @@ class Parser(Log): ...@@ -295,6 +304,15 @@ class Parser(Log):
self.log_encode(iso) self.log_encode(iso)
raw = iso.getRawIso() raw = iso.getRawIso()
self.connection.send(raw) self.connection.send(raw)
else: # dapat response
ip_port = join_ip_port(self.conf['ip'], self.conf['port'])
if ip_port in web_process:
stan_list = web_process[ip_port]
stan = from_iso.get_stan()
if stan in stan_list:
i = stan_list.index(stan)
del stan_list[i]
append_web_response(ip_port, stan, from_iso)
self.running = False self.running = False
...@@ -309,17 +327,138 @@ class ConnectionManager(BaseConnectionManager): ...@@ -309,17 +327,138 @@ class ConnectionManager(BaseConnectionManager):
conn_mgr = ConnectionManager() conn_mgr = ConnectionManager()
####### #######
# Web # # Web #
####### #######
def log_web_msg(s):
return 'Web server {}'.format(s)
def log_web_info(s): def log_web_info(s):
msg = log_web_msg(s)
log = get_log() log = get_log()
msg = 'Web server {}'.format(s)
log.info(msg) log.info(msg)
def hello_world(request): def log_web_error(s):
return Response('Hello World!') msg = log_web_msg(s)
log = get_log()
log.error(msg)
def conn_by_name(name):
conf = name_conf[name]
found_conn = None
for ip_port, conn in conn_mgr:
ip, port = ip_port.split(':')
if conf['ip'] != ip:
continue
port = int(port)
if conf['port'] != port:
continue
found_conn = conn
if not found_conn:
raise JsonRpcBankNotFound()
if not found_conn.running:
raise JsonRpcBankNotFound(message='Disconnected')
return found_conn
# Daftar job dari web request, berisi iso request
# key: ip:port, value: list of iso
web_request = {}
# Daftar job yang sedang diproses, yaitu menunggu iso response
# key: ip:port, value: list of stan (bit 11)
web_process = {}
# Daftar job yang sudah selesai, berisi iso response
# key: ip:port, value: dict of (key: stan, value: iso)
web_response = {}
def append_web_process(ip_port, iso):
stan = iso.get_stan()
if ip_port in web_process:
web_process[ip_port].append(stan)
else:
web_process[ip_port] = [stan]
def append_web_response(ip_port, stan, iso):
if ip_port in web_response:
web_response[ip_port][stan] = iso
else:
web_response[ip_port] = {stan: iso}
def web_job(conn, iso):
ip_port = join_ip_port(conn.conf['ip'], conn.conf['port'])
if ip_port in web_request:
web_request[ip_port].append(iso)
else:
web_request[ip_port] = [iso]
stan = iso.get_stan()
awal = time()
while True:
sleep(1)
if time() - awal > 5:
raise JsonRpcBillerNetwork(message='Timeout')
if ip_port not in web_response:
continue
result = web_response[ip_port]
if stan in result:
iso = result[stan]
del result[stan]
data = iso_to_dict(iso)
return dict(code=0, message='OK', data=data)
def validate_rpc(p):
if 'host' not in p:
raise JsonRpcInvalidParams()
return conn_by_name(p['host'])
def log_web_receive(request, method, p, flow='Receive'):
msg = '{} {} {} {}'.format(request.client_addr, flow, method, p)
log_web_info(msg)
def log_web_send(request, method, p):
log_web_receive(request, method, p, 'Send')
@jsonrpc_method(endpoint='rpc')
def echo(request, p):
log_web_receive(request, 'echo', p)
conn = validate_rpc(p)
iso = conn.job.echo_request()
r = web_job(conn, iso)
log_web_send(request, 'echo', r)
return r
@jsonrpc_method(endpoint='rpc')
def inquiry(request, p):
conn = validate_rpc(p)
iso = conn.job.inquiry(p)
return web_job(conn, iso)
@jsonrpc_method(endpoint='rpc')
def payment(request, p):
conn = validate_rpc(p)
iso = conn.job.payment(p)
return web_job(conn, iso)
@jsonrpc_method(endpoint='rpc')
def reversal(request, p):
conn = validate_rpc(p)
iso = conn.job.reversal(p)
return web_job(conn, iso)
web_server = {} web_server = {}
...@@ -327,18 +466,24 @@ web_server = {} ...@@ -327,18 +466,24 @@ web_server = {}
def start_web_server(): def start_web_server():
port = get_web_port() port = get_web_port()
if not port:
return
with Configurator() as config: with Configurator() as config:
config.add_route('hello', '/') config.include('pyramid_tm')
config.add_view(hello_world, route_name='hello') config.include('pyramid_rpc.jsonrpc')
config.add_jsonrpc_endpoint('rpc', '/rpc')
config.scan(__name__)
app = config.make_wsgi_app() app = config.make_wsgi_app()
web_server['listener'] = server = make_server('0.0.0.0', port, app) web_server['listener'] = server = make_server('0.0.0.0', port, app)
web_server['thread'] = Thread(target=server.serve_forever) web_server['thread'] = create_thread(server.serve_forever)
web_server['thread'].start() web_server['thread'].start()
ip_port = web_server['listener'].server_address ip_port = web_server['listener'].server_address
log_web_info('listen at {}:{}'.format(*ip_port)) log_web_info('listen at {}:{}'.format(*ip_port))
def stop_web_server(reason): def stop_web_server(reason):
if 'listener' not in web_server:
return
msg = 'stop because {}'.format(reason) msg = 'stop because {}'.format(reason)
log_web_info(msg) log_web_info(msg)
# shutdown() ini kadang tidak segera mengakhiri web server. Akan cepat # shutdown() ini kadang tidak segera mengakhiri web server. Akan cepat
...@@ -401,7 +546,14 @@ def check_job(): ...@@ -401,7 +546,14 @@ def check_job():
continue continue
iso = connection.job.get_iso() iso = connection.job.get_iso()
if not iso: if not iso:
if ip_port not in web_request:
continue
jobs = web_request[ip_port]
if not jobs:
continue continue
iso = jobs[0]
del jobs[0]
append_web_process(ip_port, iso)
connection.log_encode(iso) connection.log_encode(iso)
raw = iso.getRawIso() raw = iso.getRawIso()
connection.send(raw) connection.send(raw)
......
...@@ -30,12 +30,14 @@ requires = [ ...@@ -30,12 +30,14 @@ requires = [
'pyramid_beaker', 'pyramid_beaker',
'pyramid_mailer', 'pyramid_mailer',
'requests', 'requests',
'pyramid_rpc',
] ]
customs_require = [ customs_require = [
'http://repo.opensipkd.com/pip/opensipkd-base-0.2.tar.gz', 'http://repo.opensipkd.com/pip/opensipkd-base-0.2.tar.gz',
'http://repo.opensipkd.com/pip/opensipkd-hitung-0.1.tar.gz', 'http://repo.opensipkd.com/pip/opensipkd-hitung-0.1.tar.gz',
'http://repo.opensipkd.com/pip/opensipkd-iso8583-0.1.tar.gz', 'http://repo.opensipkd.com/pip/opensipkd-iso8583-0.1.tar.gz',
'http://repo.opensipkd.com/pip/opensipkd-jsonrpc-0.1.tar.gz',
] ]
tests_require = [ tests_require = [
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!