2025-01-21 10:13:51 +00:00
|
|
|
import http.server
|
|
|
|
import http.client
|
|
|
|
import yaml
|
|
|
|
import docker
|
|
|
|
import threading
|
2025-01-21 13:00:29 +00:00
|
|
|
import time
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from socketserver import ThreadingMixIn
|
2025-01-21 15:26:48 +00:00
|
|
|
import os
|
2025-01-21 10:13:51 +00:00
|
|
|
|
|
|
|
# Define the target server to proxy requests to
|
|
|
|
class ProxyHandler(http.server.BaseHTTPRequestHandler):
|
|
|
|
def __init__(self, configuration, docker_client):
|
2025-01-21 14:28:54 +00:00
|
|
|
global activity
|
2025-01-21 10:13:51 +00:00
|
|
|
self.configuration = configuration
|
|
|
|
self.docker_client = docker_client
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
"""Handle a request."""
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
def log_message(self, format, *args):
|
|
|
|
pass
|
|
|
|
|
2025-01-21 15:27:23 +00:00
|
|
|
def finish(self,*args,**kw):
|
|
|
|
try:
|
|
|
|
if not self.wfile.closed:
|
|
|
|
self.wfile.flush()
|
|
|
|
self.wfile.close()
|
|
|
|
except socket.error:
|
|
|
|
pass
|
|
|
|
self.rfile.close()
|
|
|
|
|
2025-01-21 10:13:51 +00:00
|
|
|
def do_GET(self):
|
|
|
|
self.handle_request('GET')
|
|
|
|
|
|
|
|
def do_POST(self):
|
|
|
|
self.handle_request('POST')
|
|
|
|
|
|
|
|
def do_PUT(self):
|
|
|
|
self.handle_request('PUT')
|
|
|
|
|
|
|
|
def do_DELETE(self):
|
|
|
|
self.handle_request('DELETE')
|
|
|
|
|
|
|
|
def do_HEAD(self):
|
|
|
|
self.handle_request('HEAD')
|
|
|
|
|
|
|
|
def handle_request(self, method):
|
|
|
|
#print(self.headers.get('Host').split(":")[0])
|
2025-01-21 15:26:48 +00:00
|
|
|
parsed_request_host = self.headers.get('Host')
|
|
|
|
|
|
|
|
if (':' in parsed_request_host):
|
|
|
|
parsed_request_host = parsed_request_host.split(":")[0]
|
|
|
|
|
|
|
|
proxy_host_configuration = next(filter(lambda host: host['domain'] == parsed_request_host, self.configuration['proxy_hosts']))
|
|
|
|
|
2025-01-21 10:13:51 +00:00
|
|
|
starting = False
|
|
|
|
for container in proxy_host_configuration['containers']:
|
2025-01-21 15:26:48 +00:00
|
|
|
container_objects = self.docker_client.containers.list(all=True, filters = { 'name' : container['container_name'] })
|
|
|
|
if (container_objects == []):
|
|
|
|
self.send_404(proxy_host_configuration['domain'])
|
|
|
|
return
|
|
|
|
|
|
|
|
container_object = container_objects[0]
|
2025-01-21 10:13:51 +00:00
|
|
|
if (container_object.status != 'running'):
|
|
|
|
print("starting container: {0}".format(container['container_name']))
|
|
|
|
container_object.start()
|
|
|
|
starting = True
|
|
|
|
|
|
|
|
if (starting == True):
|
2025-01-21 15:26:48 +00:00
|
|
|
self.send_loading(proxy_host_configuration['proxy_load_seconds'], proxy_host_configuration['domain'])
|
2025-01-21 10:34:10 +00:00
|
|
|
return
|
|
|
|
|
2025-01-21 14:28:54 +00:00
|
|
|
activity[proxy_host_configuration['domain']] = datetime.now(timezone.utc)
|
2025-01-21 10:13:51 +00:00
|
|
|
# Open a connection to the target server
|
|
|
|
conn = http.client.HTTPConnection(proxy_host_configuration['proxy_host'], proxy_host_configuration['proxy_port'])
|
|
|
|
conn.request(method, self.path, headers=self.headers)
|
|
|
|
response = conn.getresponse()
|
|
|
|
|
|
|
|
self.send_response(response.status)
|
2025-01-21 10:34:10 +00:00
|
|
|
|
|
|
|
self.send_header('host', proxy_host_configuration['proxy_host'])
|
2025-01-21 10:13:51 +00:00
|
|
|
for header, value in response.getheaders():
|
|
|
|
self.send_header(header, value)
|
|
|
|
|
|
|
|
self.end_headers()
|
2025-01-21 10:34:10 +00:00
|
|
|
self.wfile.write(response.read())
|
2025-01-21 14:28:54 +00:00
|
|
|
|
2025-01-21 10:13:51 +00:00
|
|
|
conn.close()
|
2025-01-21 15:26:48 +00:00
|
|
|
|
|
|
|
def send_404(self, service_name):
|
|
|
|
self.send_response(404)
|
|
|
|
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
|
|
|
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
|
|
|
self.send_header('Pragma', 'no-cache')
|
|
|
|
self.send_header('Expires', '0')
|
|
|
|
self.end_headers()
|
|
|
|
|
|
|
|
with open(os.path.dirname(os.path.realpath(__file__)) + '/templates/404.html', 'r') as file:
|
|
|
|
html = file.read()
|
|
|
|
html = html.replace("{{SERVICE}}", service_name)
|
|
|
|
self.wfile.write(bytes(html,"utf-8"))
|
|
|
|
|
|
|
|
self.wfile.flush()
|
|
|
|
|
|
|
|
def send_loading(self, wait_time, service_name):
|
|
|
|
self.send_response(201)
|
|
|
|
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
|
|
|
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
|
|
|
self.send_header('Pragma', 'no-cache')
|
|
|
|
self.send_header('Expires', '0')
|
|
|
|
self.send_header('refresh', wait_time)
|
|
|
|
self.end_headers()
|
|
|
|
|
|
|
|
with open(os.path.dirname(os.path.realpath(__file__)) + '/templates/wait.html', 'r') as file:
|
|
|
|
html = file.read()
|
|
|
|
self.wfile.write(bytes(html,"utf-8"))
|
|
|
|
|
|
|
|
#self.wfile.write(bytes("starting service: {0} waiting for {1}s".format(self.headers.get('Host').split(":")[0], proxy_host_configuration['proxy_timeout_seconds']),"utf-8"))
|
|
|
|
#self.wfile.write(bytes("\nlast started at: {0} ".format(activity[proxy_host_configuration['domain']]),"utf-8"))
|
|
|
|
|
|
|
|
self.wfile.flush()
|
|
|
|
|
2025-01-21 10:13:51 +00:00
|
|
|
class ThreadedHTTPServer(ThreadingMixIn, http.server.HTTPServer):
|
|
|
|
"""Handle requests in a separate thread."""
|
|
|
|
|
2025-01-21 13:00:29 +00:00
|
|
|
class BackgroundTasks(threading.Thread):
|
|
|
|
def __init__(self, configuration, docker_client):
|
|
|
|
super(BackgroundTasks, self).__init__()
|
|
|
|
self.configuration = configuration
|
|
|
|
self.docker_client = docker_client
|
|
|
|
|
|
|
|
def run(self,*args,**kwargs):
|
2025-01-21 14:28:54 +00:00
|
|
|
global activity
|
2025-01-21 13:00:29 +00:00
|
|
|
while True:
|
|
|
|
sleep_time = 900
|
|
|
|
for apps in self.configuration['proxy_hosts']:
|
|
|
|
if(sleep_time > apps['proxy_timeout_seconds']):
|
|
|
|
sleep_time = apps['proxy_timeout_seconds']
|
|
|
|
|
|
|
|
for container in apps['containers']:
|
2025-01-21 15:26:48 +00:00
|
|
|
try:
|
|
|
|
container_object = self.docker_client.containers.get(container['container_name'])
|
|
|
|
if (container_object.status == 'running'):
|
|
|
|
|
|
|
|
dt = datetime.now(timezone.utc)
|
|
|
|
if (apps['domain'] in activity):
|
|
|
|
dt = activity[apps['domain']]
|
|
|
|
|
|
|
|
diff_seconds = (datetime.now(timezone.utc) - dt).total_seconds()
|
|
|
|
if(diff_seconds > apps['proxy_timeout_seconds']):
|
|
|
|
print("stopping container: {0} ({1}) after {2}s".format(container['container_name'], container_object.id, diff_seconds))
|
|
|
|
container_object.stop()
|
|
|
|
except docker.errors.NotFound:
|
|
|
|
pass
|
2025-01-21 13:00:29 +00:00
|
|
|
|
2025-01-21 14:28:54 +00:00
|
|
|
time.sleep(sleep_time)
|
2025-01-21 13:00:29 +00:00
|
|
|
|
2025-01-21 10:13:51 +00:00
|
|
|
if __name__ == '__main__':
|
2025-01-21 14:28:54 +00:00
|
|
|
activity = {}
|
|
|
|
|
2025-01-21 10:13:51 +00:00
|
|
|
with open('config.yml', 'r') as file:
|
|
|
|
configuration = yaml.safe_load(file)
|
|
|
|
|
|
|
|
docker_client = docker.from_env()
|
|
|
|
|
2025-01-21 13:00:29 +00:00
|
|
|
t = BackgroundTasks(configuration, docker_client)
|
|
|
|
t.start()
|
|
|
|
|
2025-01-21 10:13:51 +00:00
|
|
|
# Start the reverse proxy server on port 8888
|
|
|
|
server_address = ('', configuration['proxy_port'])
|
|
|
|
proxy_handler = ProxyHandler(configuration, docker_client)
|
|
|
|
httpd = ThreadedHTTPServer(server_address, proxy_handler)
|
2025-01-21 10:34:10 +00:00
|
|
|
print('Reverse proxy server running on port {0}...'.format(configuration['proxy_port']))
|
2025-01-21 10:13:51 +00:00
|
|
|
httpd.serve_forever()
|