proxy.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """
  2. Manages all proxy-related stuff
  3. """
  4. import logging
  5. import os
  6. import subprocess
  7. import time
  8. from typing import Dict, Any
  9. from dataclasses import dataclass, field
  10. from jinja2 import Template
  11. from .backend import BackendConfig
  12. logger = logging.getLogger(__name__)
  13. @dataclass
  14. class ProxyConfig:
  15. """Reverse proxy configuration"""
  16. binary_path: str = "/usr/sbin/haproxy"
  17. template_path: str = "haproxy.cfg.tpl"
  18. working_dir: str = "/tmp/httphound"
  19. listen_addr: str = "*"
  20. listen_port: int = 4242
  21. template_vars: Dict[str, Any] = field(default_factory=dict)
  22. class ProxyManager:
  23. """Manages reverse proxy"""
  24. def __init__(self, config: ProxyConfig):
  25. self.config = config
  26. self.process = None
  27. self.config_file = None
  28. def render_config(self, backend_config: BackendConfig) -> str:
  29. """Render proxy configuration from template"""
  30. template_vars = {
  31. 'listen_addr': self.config.listen_addr,
  32. 'listen_port': self.config.listen_port,
  33. 'backend_host': backend_config.host,
  34. 'backend_port': backend_config.port,
  35. **self.config.template_vars
  36. }
  37. try:
  38. with open(self.config.template_path, 'r', encoding="utf-8") as f:
  39. template_content = f.read()
  40. except FileNotFoundError:
  41. logger.error(f"Cannot finf template {self.config.template_path}")
  42. raise
  43. template = Template(template_content)
  44. rendered_template = template.render(**template_vars)
  45. logger.debug(f"Rendered template: \n{rendered_template}\n")
  46. return rendered_template
  47. def start(self, backend_config: BackendConfig):
  48. """Start the reverse proxy"""
  49. # Create working directory
  50. logger.debug(
  51. f"Creating reverese proxy working dir: {self.config.working_dir}")
  52. os.makedirs(self.config.working_dir, exist_ok=True)
  53. # Render and write config file
  54. try:
  55. config_content = self.render_config(backend_config)
  56. except Exception as e:
  57. logger.error(f"Cannot render template: {str(e)}")
  58. raise RuntimeError(f"Cannot render template: {str(e)}") from e
  59. self.config_file = os.path.join(self.config.working_dir, "haproxy.cfg")
  60. logger.debug(f"Writing configuration file {self.config_file}")
  61. with open(self.config_file, 'w', encoding="utf-8") as f:
  62. f.write(config_content)
  63. # Start proxy process
  64. # TODO: customize
  65. cmd = [self.config.binary_path,
  66. '-V',
  67. '-db',
  68. '-f', self.config_file,
  69. ]
  70. logger.debug(f"Running proxy cmd: {cmd}")
  71. try:
  72. self.process = subprocess.Popen(
  73. cmd,
  74. stdout=subprocess.PIPE,
  75. stderr=subprocess.PIPE,
  76. cwd=self.config.working_dir
  77. )
  78. # Give proxy time to start
  79. logger.debug("Waiting 0.1s for proxy to start")
  80. time.sleep(0.1)
  81. if self.process.poll() is not None:
  82. _, stderr = self.process.communicate()
  83. raise RuntimeError(f"Proxy failed to start: {stderr.decode()}")
  84. logger.debug(f"Proxy started with PID {self.process.pid}")
  85. except FileNotFoundError as e:
  86. raise RuntimeError(
  87. f"Proxy binary not found: {self.config.binary_path}") from e
  88. def stop(self):
  89. """Stop the reverse proxy"""
  90. logger.debug("Stopping reverse proxy")
  91. if self.process and self.process.poll() is None:
  92. self.process.terminate()
  93. try:
  94. self.process.wait(timeout=5)
  95. except subprocess.TimeoutExpired:
  96. self.process.kill()
  97. self.process.wait()
  98. logger.debug("Proxy stopped")
  99. # Cleanup config file
  100. if self.config_file and os.path.exists(self.config_file):
  101. logger.debug(f"Removing config file {self.config_file}")
  102. #os.remove(self.config_file)