From c2da8348365e6b7dc6e576fce10a03c99511b230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Wed, 22 Jul 2026 08:00:18 +0400 Subject: [PATCH] feat: Flask consumer urllib + RabbitMQ HTTP API --- requirements.txt | 4 +++ site/app.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 requirements.txt create mode 100644 site/app.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9cc1ecd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Flask>=3.0 +gunicorn>=21.2 +redis>=5.0 +pymongo>=4.0 diff --git a/site/app.py b/site/app.py new file mode 100644 index 0000000..f8a59a2 --- /dev/null +++ b/site/app.py @@ -0,0 +1,92 @@ +"""Consumer — RabbitMQ HTTP API → Redis + MongoDB (no AMQP libs)""" +import json, os, base64, threading, time +from urllib.request import Request, urlopen +from flask import Flask, jsonify +import redis, pymongo + +app = Flask(__name__) + +RMQ_HOST = os.environ["RMQ_HOST"] +RMQ_USER = os.environ["RMQ_USER"] +RMQ_PASS = os.environ["RMQ_PASS"] +RMQ_QUEUE = os.environ["RMQ_QUEUE"] +RMQ_API = f"http://{RMQ_HOST}:15672/api" +AUTH = base64.b64encode(f"{RMQ_USER}:{RMQ_PASS}".encode()).decode() + +REDIS_HOST = os.environ["REDIS_HOST"] +REDIS_PORT = int(os.environ.get("REDIS_PORT", 6379)) +REDIS_PASS = os.environ.get("REDIS_PASS", "") + +MONGO_URI = os.environ["MONGO_URI"] +MONGO_DB = os.environ.get("MONGO_DB", "iot") + +consumed = 0; errors = 0 +r = None; mongo = None + +def get_redis(): + global r + if r is None: + r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASS or None, decode_responses=True) + print("[CONSUMER] Redis connected") + return r + +def get_mongo(): + global mongo + if mongo is None: + mongo = pymongo.MongoClient(MONGO_URI)[MONGO_DB]["iot_events"] + mongo.create_index("timestamp", expireAfterSeconds=604800) + print("[CONSUMER] MongoDB connected") + return mongo + +def rmq_get(url): + req = Request(url, headers={"Authorization": f"Basic {AUTH}"}) + return json.loads(urlopen(req, timeout=5).read()) + +def store(event): + rd = get_redis() + rd.hset("iot:latest", event["sensor_id"], json.dumps({ + "value": event["value"], "unit": event["unit"], "location": event["location"], "timestamp": event["timestamp"], + })) + rd.hincrby("iot:counters", event["device_type"], 1) + rd.zadd("iot:recent", {json.dumps(event): 0}) + rd.zremrangebyrank("iot:recent", 0, -1001) + get_mongo().insert_one({ + "event_id": event["event_id"], "sensor_id": event["sensor_id"], + "device_type": event["device_type"], "location": event["location"], + "value": event["value"], "unit": event["unit"], "timestamp": event["timestamp"], + }) + +def poll_loop(): + global consumed, errors + # Wait for RabbitMQ to be ready + time.sleep(10) + while True: + try: + # Get messages from queue via HTTP API + msgs = rmq_get(f"{RMQ_API}/queues/%2F/{RMQ_QUEUE}/get") + if msgs: + for m in msgs: + if m.get("payload"): + event = json.loads(base64.b64decode(m["payload"]).decode()) + store(event) + consumed += 1 + # Ack via API + rmq_get(f"{RMQ_API}/queues/%2F/{RMQ_QUEUE}/get?requeue=false&count=1") + if consumed > 0 and consumed % 10 == 0: + print(f"[CONSUMER] Processed {consumed}") + except Exception as e: + errors += 1 + print(f"[CONSUMER] Error: {e}") + time.sleep(2) + +@app.route("/") +def index(): + return jsonify({"service":"iot-consumer","status":"running","queue":RMQ_QUEUE,"consumed":consumed,"errors":errors}) + +@app.route("/health") +def health(): + return jsonify({"status":"ok","consumed":consumed}) + +if __name__ == "__main__": + threading.Thread(target=poll_loop, daemon=True).start() + app.run(host="0.0.0.0", port=int(os.environ.get("PORT",5000)))