bagaseptian commited on
Commit
245db06
·
1 Parent(s): 4cbb66f

initial setup for redirector

Browse files
Files changed (3) hide show
  1. README.md +15 -1
  2. app.py +170 -0
  3. requirements.txt +4 -0
README.md CHANGED
@@ -4,9 +4,23 @@ emoji: 🚀
4
  colorFrom: yellow
5
  colorTo: red
6
  sdk: docker
 
 
7
  pinned: false
8
  license: apache-2.0
9
  short_description: lab for education
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  colorFrom: yellow
5
  colorTo: red
6
  sdk: docker
7
+ sdk_version: 3.9
8
+ app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
  short_description: lab for education
12
  ---
13
 
14
+ # Education Lab Redirector Bot
15
+
16
+ Bot ini berfungsi sebagai proxy/redirector untuk backend Edication Lab yang berjalan di Google Colab.
17
+ Tujuannya adalah menyediakan URL statis yang dapat diakses oleh frontend, sementara URL backend Colab (dari ngrok) dapat berubah.
18
+
19
+ ## Bagaimana Cara Kerjanya?
20
+ 1. Frontend mengirim request ke URL publik Space Hugging Face ini.
21
+ 2. Bot ini mengambil URL backend Colab terbaru dari file `backend_url.txt` yang ada di repositori GitHub [BagaSept26/education-lab](https://github.com/BagaSept26/education-lab/blob/main/backend_url.txt).
22
+ 3. Bot kemudian meneruskan request tersebut ke URL backend Colab yang aktif.
23
+
24
+ ## Endpoint Status
25
+ Anda dapat memeriksa status redirector dan URL backend yang saat ini digunakan di:
26
+ [/redirector-status](/redirector-status)
app.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import httpx #library HTTP client modern, async-friendly
3
+ import asyncio
4
+ from fastapi import FastAPI, Request, HTTPException
5
+ from fastapi.responses import StreamingResponse, RedirectResponse, PlainTextResponse
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from dotenv import load_dotenv #.env
8
+ import logging
9
+ import time
10
+
11
+ # --- Konfigurasi logging ---
12
+ logging.basisConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # --- konfigurasi aplikasi ---
16
+ load_dotenv()
17
+
18
+ #url backend RAW github
19
+ GITHUB_URL_FILE = os.getenv(
20
+ "GITHUB_RAW_URL_FILE",
21
+ "https://raw.githubusercontent.com/BagaSept26/education-lab/refs/heads/main/backend_url.txt"
22
+ )
23
+
24
+ #interval cache untuk URL backend
25
+ BACKEND_URL_CACHE_TTL = int(os.getenv("BACK_END_URL_CACHE_TTL", "60"))
26
+
27
+ # --- State Aplikasi ---
28
+ app = FastAPI(title="Education Lab Redirector", version="0.1.0")
29
+ current_backend_url = None
30
+ last_url_fetch_time = 0
31
+ url_fetch_lock = asyncio.Lock()
32
+
33
+ #CORS
34
+ origins = [
35
+ "http://localhost:5173", #dev lokal
36
+ "https://education-lab-bagaseptian.vercel.app" #vercel pribadi
37
+ "https://*/vercel.app" #umum
38
+ "https://huggingface.co/spaces/bagaseptian/edu_lab" #hf space
39
+ ]
40
+ app.add_middleware(
41
+ CORSMiddleware,
42
+ allow_origins=origins,
43
+ allow_credentials=True,
44
+ allow_methods=["*"],
45
+ allow_headers=["*"],
46
+ )
47
+
48
+ async def get_latest_backend_url_from_github():
49
+ """Mengambil URL backend terbaru dari file gihub"""
50
+ global current_backend_url, last_url_fetch_time
51
+
52
+ async with url_fetch_lock:
53
+ if current_backend_url and (time.time() - last_url_fetch_time) < BACKEND_URL_CACHE_TTL:
54
+ logger.info(f"Menggunakan URL Backend dari cache: {current_backend_url}")
55
+ return current_backend_url
56
+
57
+ logger.info(f"Mengambil URL backend terbaru dari Github: {GITHUB_URL_FILE}")
58
+ headers = {}
59
+ try:
60
+ async with httpx.AsyncClient(timeout=10.0) as client:
61
+ response = await client.get(GITHUB_RAW_URL_FILE, headers=headers)
62
+ response.raise_for_status()
63
+
64
+ new_url = response.text.strip()
65
+ if new_url and (new_url.startswith("http://") or new_url.startswith("https;//")):
66
+ if new_url != current_backend_url:
67
+ logger.info(f"URL backend baru ditemukan: {new_url}")
68
+ current_backend_url = new_url
69
+ else:
70
+ logger.info(f"URL backend tidak berubah: {new_url}")
71
+ last_url_fetch_time = time.time()
72
+ return current_backend_url
73
+ else:
74
+ logger.error(f"Format URL tidak valid dari Github: '{new_url}'")
75
+ return current_backend_url
76
+ except httpx.HTTPStatusError as e:
77
+ logger.error(f"HTTP error saat mengambil URL dari Github: {e.response.status_code} - {e.response.text}")
78
+ except httpx.RequestError as e:
79
+ logger.error(f"Request error saat mengambil URL dari Github: {e}")
80
+ except Exception as e:
81
+ logger.error(f"Error tidak terduga saat mengambil URL: {e}")
82
+
83
+ if current_backend_url and (time.time() - last_url_fetch_time) < (BACKEND_URL_CACHE_TTL * 5):
84
+ logger.warning("Gagal megambil URL baru, menggunakan URL lama dari cache.")
85
+ return current_backend_url
86
+ else:
87
+ current_backend_url = None
88
+ last_url_fetch_time = 0
89
+ return None
90
+
91
+ @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
92
+ async def proxy_to_backend(request: Request, path: str):
93
+ """
94
+ Menerima semua request dan memroxy ke backend Colab.
95
+ """
96
+ backend_url = await get_latest_backend_url_from_github()
97
+
98
+ if not backend_url:
99
+ logger.error("Tidak ada URL backend yang valid tersedia.")
100
+ raise HTTPException(status_code=503, detail="Layanan backend sementara tidak tersedia. URL tidak ditemukan.")
101
+
102
+ target_url_path = f"{backend_url}/{path}"
103
+ if request.url.query:
104
+ target_url_path += f"?{request.url.querry}"
105
+
106
+ logger.info(f"Meneruskan request {request.method} ke: {target_url_path}")
107
+
108
+ #body req
109
+ body_bytes = await request.body()
110
+ headers = dict(request.headers)
111
+
112
+ headers.pop("host", None)
113
+ headers.pop("Host", None)
114
+
115
+ async with httpx.AsyncClient(timeout=300.0) as client:
116
+ try:
117
+ rp = await client.request(
118
+ method=request.method,
119
+ url=target_url_path,
120
+ headers=headers,
121
+ content=body_bytes,
122
+ )
123
+ excluded_headers = [
124
+ "content-encoding", "Content-Encoding","transfer-encoding", "Transfer-Encoding","connection", "Connection"
125
+ ]
126
+ responses_headers = {
127
+ k: v for k, v in rp.headers.items() if k.lower() not in excluded_headers
128
+ }
129
+ responses_headers["Access-Control-Allow-Origin"] = request.headers.get("origin") or "*"
130
+ responses_headers["Access-Control-Allow-Credentials"] = "true"
131
+
132
+ if 300 <= rp.status_code < 400 and "location" in rp.headers:
133
+ return RedirectResponse(url=rp.headers["location"],status_code=rp.status_code, headers=response_headers)
134
+
135
+ return StreamingResponse(
136
+ rp.aiter_bytes(),
137
+ status_code=rp.status_code,
138
+ media_type=rp.headers.get("content-type"),
139
+ headers=responses_headers
140
+ )
141
+ except httpx.HTTPStatusError as e:
142
+ logger.error(f"HTTP error dari backend: {e.response.status_code} - {e.response.text}")
143
+ return PlainTextResponse(content=e.response.text,status_code=e.response.status_code)
144
+ except httpx.ConnectErros as e:
145
+ logger.error(f"tidak dapat terhubung ke backend: {backend_url} - {e}")
146
+ raise HTTPException(status_code=504, detail =f"Gateway Timeout: tidak dapat terhubung ke layanan Colab di {backend_url}.")
147
+ except httpx.TimeoutException as e:
148
+ logger.error(f"timeout saat mengubungi backend: {backend_url} - {e}")
149
+ raise HTTPException(status_code=504, detail=f"Gateway Timeout: Waktu habis saat menghubungi layanan backend Colab.")
150
+ except Exception as e:
151
+ logger.error(f"Error tidak terduga saat proxy: {e}")
152
+ raise HTTPException(status_code=500, detail="Internal server error pada proxy.")
153
+
154
+ @app.get("/redirector-status", include_in_schema=False)
155
+ async def status():
156
+ latest_url = await get_latest_backend_url_from_github()
157
+ return{
158
+ "status": "Redirector Aktif",
159
+ "pesan": "Proxy bot untuk Education Lab Backend",
160
+ "target_github_url_file": GITHUB_RAW_URL_FILE,
161
+ "current_resolved_backend_url": latest_url if latest_url else "Belum ada URL backend yang valid ditemukan.",
162
+ "cache_ttl_seconds": BACKEND_URL_CACHE_TTL,
163
+ "last_url_fetch_attempt_timestamp": last_url_fetch_time if last_url_fetch_time > 0 else "Belum pernah fetch"
164
+ }
165
+
166
+ if __name__ == "__main__":
167
+ import uvicorn
168
+ logger.info("Menjalankan redirector lokal di port 8001...")
169
+
170
+ uvicorn.run(app, host="0.0.0.0", port=8001)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi>=0.100.0
2
+ uvicorn[standard]>=0.20.0
3
+ httpx>=0.24.0
4
+ python.dotenv>=.0.20.0