From e25250c270465bfc3b3309c733c3394b39d19bbe Mon Sep 17 00:00:00 2001 From: riri's Date: Fri, 19 Sep 2025 22:10:06 +0700 Subject: [PATCH] upcall: add Enter-to-resend and sequential 'love' combine; REST: add sub/div; SOAP: add ops & CLI; MQTT: publish JSON with lokasi=Jakarta --- MQTT/pub.py | 9 ++- MQTT/sub.py | 10 ++- REST/client.py | 11 +++- REST/server.py | 89 ++++++++++++++++++++++----- SOAP/client.py | 142 ++++++++++++++++++++++++++++++++++++++++--- SOAP/server.py | 34 ++++++++++- upcall/clientcall.py | 65 ++++++++++++++++---- upcall/servercall.py | 52 ++++++++++++++-- 8 files changed, 363 insertions(+), 49 deletions(-) diff --git a/MQTT/pub.py b/MQTT/pub.py index 747af7c..289e4e7 100644 --- a/MQTT/pub.py +++ b/MQTT/pub.py @@ -17,6 +17,7 @@ # Inisialisasi topik dan pesan suhu topic = "sister/temp" suhu = 28 # Suhu tetap 28'C +lokasi = "Jakarta" # Callback untuk koneksi def on_connect(client, userdata, flags, rc, properties=None): @@ -41,11 +42,13 @@ def on_connect(client, userdata, flags, rc, properties=None): # Loop untuk mengirim pesan setiap detik try: while True: - # Mempublikasikan suhu ke topik - message = f"Suhu: {suhu}°C" + # Mempublikasikan payload yang berisi suhu dan lokasi sebagai JSON + payload = {"suhu": suhu, "lokasi": lokasi} + import json + message = json.dumps(payload) client.publish(topic, message) print(f"Published: {message}") - + # Tunggu 1 detik sebelum mengirim lagi time.sleep(1) diff --git a/MQTT/sub.py b/MQTT/sub.py index b5cc9e4..34d77d1 100644 --- a/MQTT/sub.py +++ b/MQTT/sub.py @@ -27,7 +27,15 @@ def on_connect(client, userdata, flags, rc, properties=None): # Callback untuk pesan yang diterima def on_message(client, userdata, message, properties=None): - print(f"Received message: {message.payload.decode()} (Topic: {message.topic})") + payload = message.payload.decode() + try: + import json + data = json.loads(payload) + suhu = data.get('suhu') + lokasi = data.get('lokasi') + print(f"Diterima: Suhu = {suhu}°C, Lokasi = {lokasi} (Topic: {message.topic})") + except Exception: + print(f"Received message: {payload} (Topic: {message.topic})") # Inisialisasi klien MQTT dengan API versi terbaru client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) diff --git a/REST/client.py b/REST/client.py index f7789c0..1bb9060 100644 --- a/REST/client.py +++ b/REST/client.py @@ -9,8 +9,9 @@ import requests import argparse import sys +import os -BASE = 'http://rest-server:5151' +BASE = os.environ.get('REST_BASE', 'http://rest-server:5151') def call(endpoint, a, b): try: @@ -23,9 +24,10 @@ def call(endpoint, a, b): except Exception as e: print(f"{endpoint} exception: {e}") + def main(): parser = argparse.ArgumentParser(description="Simple REST client for add/mul endpoints") - parser.add_argument('--op', choices=['add','mul','both'], default='both', help='Operation to invoke') + parser.add_argument('--op', choices=['add','mul','both','sub','div'], default='both', help='Operation to invoke') parser.add_argument('-a', type=int, default=10) parser.add_argument('-b', type=int, default=5) args = parser.parse_args() @@ -33,6 +35,11 @@ def main(): call('add', args.a, args.b) if args.op in ('mul','both'): call('mul', args.a, args.b) + if args.op in ('sub','both'): + call('sub', args.a, args.b) + if args.op in ('div','both'): + call('div', args.a, args.b) + if __name__ == '__main__': sys.exit(main()) diff --git a/REST/server.py b/REST/server.py index 58bf530..52415a2 100644 --- a/REST/server.py +++ b/REST/server.py @@ -6,41 +6,98 @@ @author: widhi """ +import os from flask import Flask, request, jsonify # Inisialisasi aplikasi Flask app = Flask(__name__) -# Endpoint untuk pengurangan + @app.route('/add', methods=['GET']) -def sub_numbers(): +def add_numbers(): try: - # Mengambil parameter a dan b dari query string a = int(request.args.get('a')) b = int(request.args.get('b')) - result = a + b + return jsonify({'result': a + b}) + except (TypeError, ValueError): + return jsonify({'error': 'Invalid input'}), 400 - # Mengembalikan hasil dalam format JSON - return jsonify({'result': result}) + +@app.route('/sub', methods=['GET']) +def sub_numbers(): + try: + a = int(request.args.get('a')) + b = int(request.args.get('b')) + return jsonify({'result': a - b}) except (TypeError, ValueError): - # Menangani error jika input tidak valid return jsonify({'error': 'Invalid input'}), 400 - + + @app.route('/mul', methods=['GET']) def mul_numbers(): try: - # Mengambil parameter a dan b dari query string a = int(request.args.get('a')) b = int(request.args.get('b')) - result = a * b - - # Mengembalikan hasil dalam format JSON - return jsonify({'result': result}) + return jsonify({'result': a * b}) except (TypeError, ValueError): - # Menangani error jika input tidak valid return jsonify({'error': 'Invalid input'}), 400 -# Jalankan server di port 5000 + +@app.route('/div', methods=['GET']) +def div_numbers(): + try: + a = int(request.args.get('a')) + b = int(request.args.get('b')) + if b == 0: + return jsonify({'error': 'Division by zero'}), 400 + return jsonify({'result': a / b}) + except (TypeError, ValueError): + return jsonify({'error': 'Invalid input'}), 400 + + +@app.route('/calc', methods=['POST']) +def calc(): + """Generic calculator endpoint. Accepts JSON body: {"op": "add|sub|mul|div", "a": , "b": }""" + data = request.get_json(silent=True) + if not data: + return jsonify({'error': 'JSON body required'}), 400 + + op = data.get('op') + a = data.get('a') + b = data.get('b') + try: + a = float(a) + b = float(b) + except (TypeError, ValueError): + return jsonify({'error': 'Invalid input'}), 400 + + if op == 'add': + res = a + b + elif op == 'sub': + res = a - b + elif op == 'mul': + res = a * b + elif op == 'div': + if b == 0: + return jsonify({'error': 'Division by zero'}), 400 + res = a / b + elif op == 'mod': + # modulo operation + if b == 0: + return jsonify({'error': 'Division by zero'}), 400 + res = a % b + elif op == 'pow': + # exponentiation + res = a ** b + else: + return jsonify({'error': 'Unknown operation'}), 400 + + return jsonify({'result': res}) + + if __name__ == '__main__': + # Read PORT from environment to allow container/host mapping flexibility + port = int(os.environ.get('PORT', '5151')) + print(f"Starting REST server on 0.0.0.0:{port} (debug=True)") # Bind to 0.0.0.0 so container port mapping works externally - app.run(debug=True, host='0.0.0.0', port=5151) + app.run(debug=True, host='0.0.0.0', port=port) diff --git a/SOAP/client.py b/SOAP/client.py index b83f788..8880ee6 100644 --- a/SOAP/client.py +++ b/SOAP/client.py @@ -5,17 +5,143 @@ @author: widhi """ +import argparse +import os +import json +import time +from datetime import datetime from zeep import Client -# URL WSDL menggunakan nama service Docker Compose -wsdl = 'http://soap-server:8000/?wsdl' -# Membuat klien SOAP berdasarkan WSDL -client = Client(wsdl=wsdl) +def main(): + parser = argparse.ArgumentParser(description='SOAP client CLI for calculator service') + parser.add_argument('--wsdl', default=os.environ.get('SOAP_WSDL', 'http://soap-server:8000/?wsdl'), help='WSDL URL or env SOAP_WSDL') + parser.add_argument('--op', choices=['add', 'sub', 'mul', 'div','mod','pow','avg'], default=None, help='Operation (if omitted, client will print combined add+sub sentence)') + parser.add_argument('--all', action='store_true', help='Run all supported operations') + parser.add_argument('-a', type=int, default=10) + parser.add_argument('-b', type=int, default=5) + parser.add_argument('--repeat', type=int, default=1, help='Repeat each operation N times and report average latency') + parser.add_argument('--combined', action='store_true', help='Print combined sentence for add and sub in Indonesian') + parser.add_argument('--json-output', action='store_true', help='Print output as JSON') + parser.add_argument('--verbose', action='store_true', help='Print verbose output (timestamp, wsdl, latency)') + args = parser.parse_args() -# Memanggil metode penjumlahan dari layanan SOAP server -result = client.service.add(10, 5) + client = Client(wsdl=args.wsdl) + # If user didn't provide --op, --all, or --combined, default to combined sentence + if args.op is None and not args.all and not args.combined: + args.combined = True + try: + ops = ['add', 'sub', 'mul', 'div', 'mod', 'pow', 'avg'] -# Menampilkan hasil penjumlahan -print(f'Hasil penjumlahan dari server SOAP: {result}') + def call_one(op_name): + # call op_name once and return (result, latency_ms) + start = time.perf_counter() + if op_name == 'add': + r = client.service.add(args.a, args.b) + elif op_name == 'sub': + r = client.service.sub(args.a, args.b) + elif op_name == 'mul': + r = client.service.mul(args.a, args.b) + elif op_name == 'div': + r = client.service.div(args.a, args.b) + elif op_name == 'mod': + r = client.service.mod(args.a, args.b) + elif op_name == 'pow': + r = client.service.pow(args.a, args.b) + elif op_name == 'avg': + r = client.service.avg(args.a, args.b) + else: + raise ValueError('Unknown op') + end = time.perf_counter() + return r, (end - start) * 1000.0 + + results = [] + + if args.combined: + # Call add and sub and print combined Indonesian sentence + # perform repeats and take the last result for display (and avg latency) + def call_n(op_name): + total_latency = 0.0 + res_val = None + for i in range(max(1, args.repeat)): + r, lat = call_one(op_name) + total_latency += lat + res_val = r + avg_latency = total_latency / max(1, args.repeat) + return res_val, avg_latency + + add_res, add_lat = call_n('add') + sub_res, sub_lat = call_n('sub') + + if args.json_output: + out = { + 'timestamp': datetime.utcnow().isoformat() + 'Z', + 'wsdl': args.wsdl, + 'add': add_res, + 'sub': sub_res, + 'add_latency_ms': add_lat, + 'sub_latency_ms': sub_lat, + 'combined': f"hasil penjumlahan dengan SOAP = {add_res} dan hasil pengurangan dengan SOAP = {sub_res}" + } + print(json.dumps(out, indent=2)) + else: + print(f"hasil penjumlahan dengan SOAP = {add_res} dan hasil pengurangan dengan SOAP = {sub_res}") + return + + if args.all: + for op_name in ops: + total_latency = 0.0 + total_res = 0.0 + res_val = None + for i in range(max(1, args.repeat)): + r, lat = call_one(op_name) + total_latency += lat + try: + total_res += float(r) + except Exception: + total_res += 0.0 + res_val = r + avg_latency = total_latency / max(1, args.repeat) + avg_result = total_res / max(1, args.repeat) + results.append({'op': op_name, 'a': args.a, 'b': args.b, 'result': res_val, 'avg_result': avg_result, 'latency_ms': avg_latency}) + else: + total_latency = 0.0 + total_res = 0.0 + res_val = None + for i in range(max(1, args.repeat)): + r, lat = call_one(args.op) + total_latency += lat + try: + total_res += float(r) + except Exception: + total_res += 0.0 + res_val = r + avg_latency = total_latency / max(1, args.repeat) + avg_result = total_res / max(1, args.repeat) + results.append({'op': args.op, 'a': args.a, 'b': args.b, 'result': res_val, 'avg_result': avg_result, 'latency_ms': avg_latency}) + + # Output + if args.json_output: + out = { + 'timestamp': datetime.utcnow().isoformat() + 'Z', + 'wsdl': args.wsdl, + 'repeat': args.repeat, + 'results': results, + } + print(json.dumps(out, indent=2)) + else: + if args.verbose: + print(f"WSDL: {args.wsdl}") + print(f"Timestamp: {datetime.utcnow().isoformat()}Z") + for r in results: + if args.verbose: + print(f"{r['op']}({r['a']},{r['b']}) = {r['result']} (avg_result={r['avg_result']}, latency={r['latency_ms']:.2f} ms)") + else: + print(f"{r['op']}({r['a']},{r['b']}) = {r['result']}") + except Exception as e: + print('SOAP call failed:', e) + + +if __name__ == '__main__': + main() diff --git a/SOAP/server.py b/SOAP/server.py index 0c81207..27293b1 100644 --- a/SOAP/server.py +++ b/SOAP/server.py @@ -6,7 +6,7 @@ @author: widhi """ -from spyne import Application, rpc, ServiceBase, Integer +from spyne import Application, rpc, ServiceBase, Integer, Double from spyne.protocol.soap import Soap11 from spyne.server.wsgi import WsgiApplication @@ -16,6 +16,38 @@ class CalculatorService(ServiceBase): def add(ctx, a, b): return a + b + @rpc(Integer, Integer, _returns=Integer) + def sub(ctx, a, b): + return a - b + + @rpc(Integer, Integer, _returns=Integer) + def mul(ctx, a, b): + return a * b + + @rpc(Integer, Integer, _returns=Integer) + def div(ctx, a, b): + # simple integer division; return 0 on division by zero + try: + return a // b + except ZeroDivisionError: + return 0 + + @rpc(Integer, Integer, _returns=Integer) + def mod(ctx, a, b): + try: + return a % b + except ZeroDivisionError: + return 0 + + @rpc(Integer, Integer, _returns=Double) + def pow(ctx, a, b): + # exponentiation; return float to avoid overflow issues + return float(a) ** float(b) + + @rpc(Integer, Integer, _returns=Double) + def avg(ctx, a, b): + return (float(a) + float(b)) / 2.0 + # Membuat aplikasi SOAP dengan protokol Soap11 app = Application([CalculatorService], tns='spyne.examples.calculator', diff --git a/upcall/clientcall.py b/upcall/clientcall.py index 7470404..60970bb 100644 --- a/upcall/clientcall.py +++ b/upcall/clientcall.py @@ -7,23 +7,64 @@ """ import socket +import argparse -def client_program(): + +def client_program(host, port, double_message=None): client_socket = socket.socket() # Use docker compose service DNS instead of a hardcoded external IP - client_socket.connect(('upcall-server', 4141)) - - message = input("Enter message: ") - + client_socket.connect((host, port)) + + if double_message: + # send two messages in one payload separated by '||' + payload = double_message + client_socket.send(payload.encode()) + data = client_socket.recv(1024).decode() + print('Received upcall from server:', data) + client_socket.close() + return + + print("Type a message and press Enter to send. Press Enter with empty input to resend the previous message. Type 'bye' to quit.") + last_message = None + message = input("Enter message: ") + while message.lower().strip() != 'bye': - client_socket.send(message.encode()) - data = client_socket.recv(1024).decode() - + # If user pressed Enter without typing anything, resend last message + if message.strip() == '': + if last_message is None: + # nothing to resend; prompt again + print('(no previous message to resend)') + message = input("Enter message: ") + continue + message_to_send = last_message + print(f"Resending previous message: '{message_to_send}'") + else: + message_to_send = message + + # If we have a previous message and the user typed a new one, + # send them together as a single payload so the server can insert 'love' between them. + if last_message is not None and message_to_send.strip() != last_message.strip(): + combined = f"{last_message}||{message_to_send}" + print(f"Sending combined payload: '{combined}'") + client_socket.send(combined.encode()) + else: + # normal single-message send (including resends) + client_socket.send(message_to_send.encode()) + + data = client_socket.recv(1024).decode() + print('Received upcall from server:', data) # Simulating upcall response - - message = input("Enter another message: ") - + + last_message = message_to_send + message = input("Enter another message (or press Enter to resend): ") + client_socket.close() + if __name__ == '__main__': - client_program() + parser = argparse.ArgumentParser(description='Upcall client') + parser.add_argument('--host', default='upcall-server', help='Server host') + parser.add_argument('--port', type=int, default=4141, help='Server port') + parser.add_argument('--double', help='Send two messages separated by "||" as a single payload') + args = parser.parse_args() + client_program(args.host, args.port, args.double) diff --git a/upcall/servercall.py b/upcall/servercall.py index d1b34fb..6d02170 100644 --- a/upcall/servercall.py +++ b/upcall/servercall.py @@ -7,25 +7,65 @@ """ import socket +import os +import sys + def server_program(): server_socket = socket.socket() # Bind to all interfaces so client container can reach it - server_socket.bind(('0.0.0.0', 4141)) - + base_port = int(os.environ.get('PORT', '4141')) + bound_port = None + # try a few ports if the preferred one is already in use + for attempt in range(base_port, base_port + 5): + try: + server_socket.bind(('0.0.0.0', attempt)) + bound_port = attempt + break + except OSError as e: + print(f"Port {attempt} not available: {e}") + continue + + if bound_port is None: + print(f"Could not bind to any port in range {base_port}-{base_port+4}. Exiting.") + sys.exit(1) + server_socket.listen(1) - print("Upcall server listening on 0.0.0.0:4141 (DNS: upcall-server:4141)") + print(f"Upcall server listening on 0.0.0.0:{bound_port} (DNS: upcall-server:{bound_port})") conn, address = server_socket.accept() print("Connection from:", address) + # Keep track of the previous message for this connection so that + # two sequential sends (first message then second message) can be + # combined into a single 'love' response: ' love '. + prev_msg = None while True: data = conn.recv(1024).decode() if not data: break + data = data.strip() print("Received from client:", data) - - # Simulate upcall (sending an event message to client) - upcall_message = "Upcall event: Processing " + data + + # If client sent two messages in one payload separated by '||', + # insert the word 'love' between them and send that back. + if '||' in data: + parts = data.split('||', 1) + left = parts[0].strip() + right = parts[1].strip() + upcall_message = f"{left} love {right}" + # reset prev_msg after handling a combined payload + prev_msg = None + else: + if prev_msg is None: + # store the first message and acknowledge receipt + prev_msg = data + upcall_message = f"Received: {data}" + else: + # we have a previous message; combine with current + upcall_message = f"{prev_msg} love {data}" + # reset prev_msg after combining + prev_msg = None + conn.send(upcall_message.encode()) conn.close()