pyproxytools 0.4.4__tar.gz → 0.4.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {pyproxytools-0.4.4/pyproxytools.egg-info → pyproxytools-0.4.5}/PKG-INFO +1 -1
  2. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/__init__.py +1 -1
  3. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/handlers/client.py +19 -18
  4. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/handlers/http.py +15 -6
  5. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/handlers/https.py +25 -6
  6. pyproxytools-0.4.5/pyproxy/monitoring/__init__.py +38 -0
  7. pyproxytools-0.4.5/pyproxy/monitoring/auth.py +31 -0
  8. pyproxytools-0.4.5/pyproxy/monitoring/monitor.py +196 -0
  9. pyproxytools-0.4.5/pyproxy/monitoring/routes.py +78 -0
  10. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/server.py +1 -1
  11. {pyproxytools-0.4.4 → pyproxytools-0.4.5/pyproxytools.egg-info}/PKG-INFO +1 -1
  12. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxytools.egg-info/SOURCES.txt +3 -1
  13. pyproxytools-0.4.4/pyproxy/monitoring/web.py +0 -279
  14. pyproxytools-0.4.4/tests/utils/__init__.py +0 -0
  15. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/LICENSE +0 -0
  16. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/README.md +0 -0
  17. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/benchmark/benchmark.py +0 -0
  18. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/benchmark/utils/__init__.py +0 -0
  19. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/benchmark/utils/html.py +0 -0
  20. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/benchmark/utils/req.py +0 -0
  21. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproject.toml +0 -0
  22. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/__main__.py +0 -0
  23. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/handlers/__init__.py +0 -0
  24. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/modules/__init__.py +0 -0
  25. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/modules/cancel_inspect.py +0 -0
  26. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/modules/custom_header.py +0 -0
  27. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/modules/filter.py +0 -0
  28. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/modules/shortcuts.py +0 -0
  29. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/pyproxy.py +0 -0
  30. {pyproxytools-0.4.4/pyproxy/monitoring → pyproxytools-0.4.5/pyproxy/utils}/__init__.py +0 -0
  31. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/utils/args.py +0 -0
  32. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/utils/config.py +0 -0
  33. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/utils/crypto.py +0 -0
  34. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/utils/http_req.py +0 -0
  35. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxy/utils/logger.py +0 -0
  36. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxytools.egg-info/dependency_links.txt +0 -0
  37. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxytools.egg-info/entry_points.txt +0 -0
  38. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxytools.egg-info/requires.txt +0 -0
  39. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/pyproxytools.egg-info/top_level.txt +0 -0
  40. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/requirements.txt +0 -0
  41. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/setup.cfg +0 -0
  42. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/setup.py +0 -0
  43. {pyproxytools-0.4.4/pyproxy/utils → pyproxytools-0.4.5/tests/modules}/__init__.py +0 -0
  44. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/tests/modules/test_cancel_inspect.py +0 -0
  45. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/tests/modules/test_custom_header.py +0 -0
  46. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/tests/modules/test_filter.py +0 -0
  47. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/tests/modules/test_shortcuts.py +0 -0
  48. {pyproxytools-0.4.4/tests/modules → pyproxytools-0.4.5/tests/utils}/__init__.py +0 -0
  49. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/tests/utils/test_crypto.py +0 -0
  50. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/tests/utils/test_http_req.py +0 -0
  51. {pyproxytools-0.4.4 → pyproxytools-0.4.5}/tests/utils/test_logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyproxytools
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Lightweight and fast python web proxy
5
5
  Author: pyproxytools
6
6
  License-Expression: MIT
@@ -5,7 +5,7 @@ that holds the current version number of the application.
5
5
 
6
6
  import os
7
7
 
8
- __version__ = "0.4.4"
8
+ __version__ = "0.4.5"
9
9
 
10
10
  if os.path.isdir("pyproxy/monitoring"):
11
11
  __slim__ = False
@@ -90,24 +90,25 @@ class ProxyHandlers:
90
90
  Args:
91
91
  client_socket (socket): The socket object for the client connection.
92
92
  """
93
- request = client_socket.recv(4096)
93
+ try:
94
+ client_socket.settimeout(10)
95
+ request = client_socket.recv(4096)
94
96
 
95
- if not request:
96
- self.console_logger.debug("No request received, closing connection.")
97
+ if not request:
98
+ return
99
+
100
+ first_line = request.decode(errors="ignore").split("\n")[0]
101
+ if first_line.startswith("CONNECT"):
102
+ https_handler = self._create_handler(
103
+ HttpsHandler,
104
+ ssl_config=self.ssl_config,
105
+ cancel_inspect_queue=self.cancel_inspect_queue,
106
+ cancel_inspect_result_queue=self.cancel_inspect_result_queue,
107
+ )
108
+ https_handler.handle_https_connection(client_socket, first_line)
109
+ else:
110
+ http_handler = self._create_handler(HttpHandler)
111
+ http_handler.handle_http_request(client_socket, request)
112
+ finally:
97
113
  client_socket.close()
98
114
  self.active_connections.pop(threading.get_ident(), None)
99
- return
100
-
101
- first_line = request.decode(errors="ignore").split("\n")[0]
102
-
103
- if first_line.startswith("CONNECT"):
104
- https_handler = self._create_handler(
105
- HttpsHandler,
106
- ssl_config=self.ssl_config,
107
- cancel_inspect_queue=self.cancel_inspect_queue,
108
- cancel_inspect_result_queue=self.cancel_inspect_result_queue,
109
- )
110
- https_handler.handle_https_connection(client_socket, first_line)
111
- else:
112
- http_handler = self._create_handler(HttpHandler)
113
- http_handler.handle_http_request(client_socket, request)
@@ -202,15 +202,24 @@ class HttpHandler:
202
202
  server_port = parsed_url.port or (
203
203
  443 if parsed_url.scheme == "https" else 80
204
204
  )
205
+
206
+ try:
207
+ ip_address = socket.gethostbyname(server_host)
208
+ except socket.gaierror:
209
+ ip_address = server_host
210
+
205
211
  thread_id = threading.get_ident()
206
212
 
207
213
  if thread_id in self.active_connections:
208
- self.active_connections[thread_id] = {
209
- "target_ip": server_host,
210
- "target_port": server_port,
211
- "bytes_sent": 0,
212
- "bytes_received": 0,
213
- }
214
+ self.active_connections[thread_id].update(
215
+ {
216
+ "target_ip": ip_address,
217
+ "target_domain": server_host,
218
+ "target_port": server_port,
219
+ "bytes_sent": 0,
220
+ "bytes_received": 0,
221
+ }
222
+ )
214
223
 
215
224
  try:
216
225
  server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -206,6 +206,21 @@ class HttpsHandler:
206
206
  if self._is_blocked(f"{server_host}{path}"):
207
207
  return None, full_url, True
208
208
 
209
+ if not self.logger_config.no_logging_access:
210
+ method, domain_port, protocol = first_line.split(" ")
211
+ domain, port = domain_port.split(":")
212
+ self.logger_config.access_logger.info(
213
+ "",
214
+ extra={
215
+ "ip_src": ssl_client_socket.getpeername()[0],
216
+ "url": full_url,
217
+ "method": method,
218
+ "domain": server_host,
219
+ "port": port,
220
+ "protocol": protocol,
221
+ },
222
+ )
223
+
209
224
  return first_request, full_url, False
210
225
  except Exception as e:
211
226
  self.logger_config.error_logger.error(f"SSL request processing error : {e}")
@@ -231,10 +246,13 @@ class HttpsHandler:
231
246
  not_inspect = self._should_skip_inspection(server_host)
232
247
 
233
248
  thread_id = threading.get_ident()
234
- self.active_connections[thread_id] = {
235
- "bytes_sent": 0,
236
- "bytes_received": 0,
237
- }
249
+ self.active_connections[thread_id].update(
250
+ {
251
+ "target_domain": server_host,
252
+ "bytes_sent": 0,
253
+ "bytes_received": 0,
254
+ }
255
+ )
238
256
 
239
257
  if self.ssl_config.ssl_inspect and not not_inspect:
240
258
  try:
@@ -317,13 +335,13 @@ class HttpsHandler:
317
335
  )
318
336
 
319
337
  if not self.logger_config.no_logging_access:
320
- _, _, protocol = first_line.split(" ")
338
+ method, _, protocol = first_line.split(" ")
321
339
  self.logger_config.access_logger.info(
322
340
  "",
323
341
  extra={
324
342
  "ip_src": client_ip,
325
343
  "url": target,
326
- "method": "CONNECT",
344
+ "method": method,
327
345
  "domain": server_host,
328
346
  "port": server_port,
329
347
  "protocol": protocol,
@@ -397,6 +415,7 @@ class HttpsHandler:
397
415
  data
398
416
  )
399
417
  except (socket.error, OSError):
418
+ self.logger_config.console_logger("error")
400
419
  client_socket.close()
401
420
  server_socket.close()
402
421
  self.active_connections.pop(threading.get_ident(), None)
@@ -0,0 +1,38 @@
1
+ """
2
+ pyproxy.monitoring.__init__.py
3
+
4
+ Provides a monitoring system for the ProxyServer, exposing information about
5
+ processes, threads, active connections, and subprocesses. Implements an HTTP
6
+ server using Flask to provide monitoring endpoints.
7
+ """
8
+
9
+ import logging
10
+ from flask import Flask
11
+ from .monitor import ProxyMonitor
12
+ from .auth import create_basic_auth
13
+ from .routes import register_routes
14
+
15
+
16
+ def start_flask_server(proxy_server, flask_port, flask_pass, debug) -> None:
17
+ """
18
+ Launches a Flask HTTP server to monitor the ProxyServer.
19
+
20
+ The server exposes endpoints that provide status information about
21
+ the proxy server, including process details, thread information,
22
+ subprocess statuses, and active connections.
23
+
24
+ Args:
25
+ proxy_server (ProxyServer): The ProxyServer instance to monitor.
26
+ flask_port (int): The port number on which the Flask server will listen.
27
+ flask_pass (str): The password used for basic HTTP authentication.
28
+ debug (bool): Flag to enable or disable Flask debug mode.
29
+ """
30
+ auth = create_basic_auth(flask_pass)
31
+
32
+ app = Flask(__name__, static_folder="static")
33
+ if not debug:
34
+ log = logging.getLogger("werkzeug")
35
+ log.setLevel(logging.ERROR)
36
+
37
+ register_routes(app, auth, proxy_server, ProxyMonitor)
38
+ app.run(host="0.0.0.0", port=flask_port) # nosec
@@ -0,0 +1,31 @@
1
+ """
2
+ pyproxy.monitoring.auth.py
3
+
4
+ Provides HTTP Basic Authentication setup for the monitoring Flask server,
5
+ using a single hardcoded user 'admin' with a hashed password.
6
+ """
7
+
8
+ from flask_httpauth import HTTPBasicAuth
9
+ from werkzeug.security import check_password_hash, generate_password_hash
10
+
11
+
12
+ def create_basic_auth(password: str):
13
+ """
14
+ Creates and configures an HTTPBasicAuth instance with a single user.
15
+
16
+ Args:
17
+ password (str): The password for the 'admin' user.
18
+
19
+ Returns:
20
+ HTTPBasicAuth: Configured HTTPBasicAuth instance for use in Flask.
21
+ """
22
+ auth = HTTPBasicAuth()
23
+ users = {"admin": generate_password_hash(password)}
24
+
25
+ @auth.verify_password
26
+ def verify_password(username, passwd):
27
+ if username in users and check_password_hash(users.get(username), passwd):
28
+ return username
29
+ return None
30
+
31
+ return auth
@@ -0,0 +1,196 @@
1
+ """
2
+ pyproxy.monitoring.monitor
3
+
4
+ This module defines the ProxyMonitor class, which provides monitoring capabilities
5
+ for the ProxyServer. It collects and exposes real-time information about the
6
+ main process, threads, subprocesses, and active client connections using `psutil`,
7
+ `threading`, and `multiprocessing` libraries.
8
+ """
9
+
10
+ import os
11
+ import threading
12
+ import multiprocessing
13
+ from datetime import datetime
14
+ from typing import List, Dict, Union
15
+ import psutil
16
+
17
+
18
+ class ProxyMonitor:
19
+ """
20
+ Monitors the status of the ProxyServer, including details about
21
+ the main process, threads, subprocesses, and active client connections.
22
+
23
+ Args:
24
+ proxy_server (ProxyServer): The instance of the ProxyServer to monitor.
25
+ """
26
+
27
+ def __init__(self, proxy_server):
28
+ self.proxy_server = proxy_server
29
+
30
+ def get_process_info(
31
+ self,
32
+ ) -> Dict[str, Union[int, str, List[Dict[str, Union[int, str]]]]]:
33
+ """
34
+ Retrieves overall process information for the ProxyServer,
35
+ including the PID, name, status, and details about threads,
36
+ subprocesses, and active connections.
37
+
38
+ Returns:
39
+ dict: A dictionary containing the process information.
40
+ """
41
+ process_info = {
42
+ "pid": os.getpid(),
43
+ "name": "ProxyServer",
44
+ "status": "running",
45
+ "start_time": datetime.fromtimestamp(
46
+ psutil.Process(os.getpid()).create_time()
47
+ ).strftime("%Y-%m-%d %H:%M:%S"),
48
+ "threads": self.get_threads_info(),
49
+ "subprocesses": self.get_subprocesses_info(),
50
+ "active_connections": self.get_active_connections(),
51
+ }
52
+ return process_info
53
+
54
+ def get_threads_info(self) -> List[Dict[str, Union[int, str]]]:
55
+ """
56
+ Retrieves information about the threads running in the ProxyServer.
57
+
58
+ Returns:
59
+ list: A list of dictionaries, each containing information
60
+ about a thread.
61
+ """
62
+ threads_info = []
63
+ for thread in threading.enumerate():
64
+ threads_info.append(
65
+ {
66
+ "thread_id": thread.ident,
67
+ "name": thread.name,
68
+ "status": self.get_thread_status(thread),
69
+ }
70
+ )
71
+ return threads_info
72
+
73
+ def get_thread_status(self, thread: threading.Thread) -> str:
74
+ """
75
+ Gets the status of a given thread.
76
+
77
+ Args:
78
+ thread (threading.Thread): The thread whose status is to be retrieved.
79
+
80
+ Returns:
81
+ str: The status of the thread ('running', 'terminated', or 'unknown').
82
+ """
83
+ try:
84
+ if thread.is_alive():
85
+ return "running"
86
+ return "terminated"
87
+ except AttributeError:
88
+ return "unknown"
89
+
90
+ def get_subprocesses_info(
91
+ self,
92
+ ) -> Dict[str, Dict[str, Union[str, List[Dict[str, Union[int, str]]]]]]:
93
+ """
94
+ Retrieves the status of the ProxyServer's subprocesses, including
95
+ filtering, shortcuts, cancel inspection, and custom header processes.
96
+
97
+ Returns:
98
+ dict: A dictionary containing subprocess statuses.
99
+ """
100
+ subprocesses_info = {}
101
+
102
+ subprocesses = {
103
+ "filter": self.proxy_server.filter_proc,
104
+ "shortcuts": self.proxy_server.shortcuts_proc,
105
+ "cancel_inspect": self.proxy_server.cancel_inspect_proc,
106
+ "custom_header": self.proxy_server.custom_header_proc,
107
+ }
108
+
109
+ for name, process in subprocesses.items():
110
+ if process is not None and process.is_alive():
111
+ subprocesses_info[name] = self.get_subprocess_status(process, name)
112
+ return subprocesses_info
113
+
114
+ def get_subprocess_status(
115
+ self, process: multiprocessing.Process, name: str
116
+ ) -> Dict[str, Union[str, None, List[Dict[str, Union[int, str]]]]]:
117
+ """
118
+ Retrieves the status of a subprocess.
119
+
120
+ Args:
121
+ process (multiprocessing.Process): The subprocess to check.
122
+ name (str): The name of the subprocess.
123
+
124
+ Returns:
125
+ dict: A dictionary containing the subprocess status.
126
+ """
127
+ if process is None:
128
+ return {"status": "not started", "name": name, "threads": []}
129
+ try:
130
+ status = "running" if process.is_alive() else "terminated"
131
+ threads_info = self.get_subprocess_threads_info(process)
132
+ except AttributeError:
133
+ status = "terminated"
134
+ threads_info = []
135
+ return {
136
+ "pid": process.pid if hasattr(process, "pid") else None,
137
+ "status": status,
138
+ "name": name,
139
+ "threads": threads_info,
140
+ }
141
+
142
+ def get_subprocess_threads_info(
143
+ self, process: multiprocessing.Process
144
+ ) -> List[Dict[str, Union[int, str]]]:
145
+ """
146
+ Retrieves the threads associated with a subprocess.
147
+
148
+ Args:
149
+ process (multiprocessing.Process): The subprocess to check.
150
+
151
+ Returns:
152
+ list: A list of dictionaries containing thread information.
153
+ """
154
+ threads_info = []
155
+ try:
156
+ for proc_thread in psutil.Process(process.pid).threads():
157
+ threads_info.append(
158
+ {
159
+ "thread_id": proc_thread.id,
160
+ "name": f"Thread-{proc_thread.id}",
161
+ "status": self.get_thread_status_by_pid(proc_thread.id),
162
+ }
163
+ )
164
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
165
+ pass
166
+ return threads_info
167
+
168
+ def get_thread_status_by_pid(self, thread_id: int) -> str:
169
+ """
170
+ Attempts to retrieve the status of a thread by its PID.
171
+
172
+ Args:
173
+ thread_id (int): The thread's ID.
174
+
175
+ Returns:
176
+ str: The status of the thread ('running' or 'terminated').
177
+ """
178
+ try:
179
+ process = psutil.Process(thread_id)
180
+ if process.is_running():
181
+ return "running"
182
+ return "terminated"
183
+ except psutil.NoSuchProcess:
184
+ return "terminated"
185
+
186
+ def get_active_connections(self) -> List[Dict[str, Union[int, Dict]]]:
187
+ """
188
+ Retrieves information about the active client connections to the ProxyServer.
189
+
190
+ Returns:
191
+ list: A list of dictionaries containing information about active connections.
192
+ """
193
+ return [
194
+ {"thread_id": thread_id, **conn}
195
+ for thread_id, conn in self.proxy_server.active_connections.items()
196
+ ]
@@ -0,0 +1,78 @@
1
+ """
2
+ pyproxy.monitoring.routes.py
3
+
4
+ Defines and registers monitoring-related routes for the Flask application,
5
+ including endpoints for system information, configuration, and a secured
6
+ HTML-based index page.
7
+ """
8
+
9
+ from flask import jsonify, render_template
10
+
11
+
12
+ def register_routes(app, auth, proxy_server, ProxyMonitor):
13
+ """
14
+ Registers the monitoring routes to the Flask app, secured with HTTP Basic Auth.
15
+
16
+ Args:
17
+ app (Flask): The Flask application instance.
18
+ auth (HTTPBasicAuth): The HTTP Basic Auth instance used to secure routes.
19
+ proxy_server (ProxyServer): The running ProxyServer instance to monitor.
20
+ ProxyMonitor (class): The monitoring class used to gather runtime information.
21
+ """
22
+
23
+ @app.route("/")
24
+ @auth.login_required
25
+ def index():
26
+ """
27
+ Serves the main index HTML page for the monitoring dashboard.
28
+
29
+ Returns:
30
+ Response: Rendered HTML page.
31
+ """
32
+ return render_template("index.html")
33
+
34
+ @app.route("/monitoring", methods=["GET"])
35
+ @auth.login_required
36
+ def monitoring():
37
+ """
38
+ Provides real-time monitoring information about the ProxyServer,
39
+ including process, thread, and connection status.
40
+
41
+ Returns:
42
+ Response: JSON object containing monitoring data.
43
+ """
44
+ monitor = ProxyMonitor(proxy_server)
45
+ return jsonify(monitor.get_process_info())
46
+
47
+ @app.route("/config", methods=["GET"])
48
+ @auth.login_required
49
+ def config():
50
+ """
51
+ Returns the current configuration of the ProxyServer, including
52
+ host, port, debug mode, and optional components like logger, filter,
53
+ and SSL configuration.
54
+
55
+ Returns:
56
+ Response: JSON object containing configuration data.
57
+ """
58
+ config_data = {
59
+ "host": proxy_server.host_port[0],
60
+ "port": proxy_server.host_port[1],
61
+ "debug": proxy_server.debug,
62
+ "html_403": proxy_server.html_403,
63
+ "logger_config": (
64
+ proxy_server.logger_config.to_dict()
65
+ if proxy_server.logger_config
66
+ else None
67
+ ),
68
+ "filter_config": (
69
+ proxy_server.filter_config.to_dict()
70
+ if proxy_server.filter_config
71
+ else None
72
+ ),
73
+ "ssl_config": (
74
+ proxy_server.ssl_config.to_dict() if proxy_server.ssl_config else None
75
+ ),
76
+ "flask_port": proxy_server.monitoring_config.flask_port,
77
+ }
78
+ return jsonify(config_data)
@@ -27,7 +27,7 @@ if not __slim__:
27
27
  if not __slim__:
28
28
  from pyproxy.modules.custom_header import custom_header_process
29
29
  if not __slim__:
30
- from pyproxy.monitoring.web import start_flask_server
30
+ from pyproxy.monitoring import start_flask_server
31
31
 
32
32
 
33
33
  class ProxyServer:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyproxytools
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Lightweight and fast python web proxy
5
5
  Author: pyproxytools
6
6
  License-Expression: MIT
@@ -21,7 +21,9 @@ pyproxy/modules/custom_header.py
21
21
  pyproxy/modules/filter.py
22
22
  pyproxy/modules/shortcuts.py
23
23
  pyproxy/monitoring/__init__.py
24
- pyproxy/monitoring/web.py
24
+ pyproxy/monitoring/auth.py
25
+ pyproxy/monitoring/monitor.py
26
+ pyproxy/monitoring/routes.py
25
27
  pyproxy/utils/__init__.py
26
28
  pyproxy/utils/args.py
27
29
  pyproxy/utils/config.py
@@ -1,279 +0,0 @@
1
- """
2
- pyproxy.monitoring.web.py
3
-
4
- This module defines a monitoring system for the ProxyServer that provides
5
- information about the server's processes, threads, active connections, and
6
- subprocesses. It includes an HTTP server implemented with Flask to expose
7
- monitoring endpoints for the proxy server.
8
- """
9
-
10
- import os
11
- import threading
12
- import multiprocessing
13
- import logging
14
- from datetime import datetime
15
- from typing import List, Dict, Union
16
- from flask import Flask, jsonify, render_template
17
- from flask_httpauth import HTTPBasicAuth
18
- from werkzeug.security import check_password_hash, generate_password_hash
19
- import psutil
20
-
21
-
22
- def start_flask_server(proxy_server, flask_port, flask_pass, debug) -> None:
23
- """
24
- Starts the Flask server for monitoring the ProxyServer. It creates and
25
- runs an HTTP server that exposes the proxy server's status, including
26
- process and thread details, subprocess statuses, and active connections.
27
-
28
- Args:
29
- proxy_server (ProxyServer): The ProxyServer instance to monitor.
30
-
31
- The server will expose two routes:
32
- - '/' : Renders a simple index page.
33
- - '/monitoring' : Returns a JSON response with the monitoring data.
34
- """
35
-
36
- class ProxyMonitor:
37
- """
38
- Monitors the status of the ProxyServer, including process, thread,
39
- and subprocess information, as well as active client connections.
40
-
41
- Args:
42
- proxy_server (ProxyServer): The ProxyServer instance to monitor.
43
- """
44
-
45
- def __init__(self, proxy_server):
46
- self.proxy_server = proxy_server
47
-
48
- def get_process_info(
49
- self,
50
- ) -> Dict[str, Union[int, str, List[Dict[str, Union[int, str]]]]]:
51
- """
52
- Retrieves overall process information for the ProxyServer,
53
- including the PID, name, status, and details about threads,
54
- subprocesses, and active connections.
55
-
56
- Returns:
57
- dict: A dictionary containing the process information.
58
- """
59
- process_info = {
60
- "pid": os.getpid(),
61
- "name": "ProxyServer",
62
- "status": "running",
63
- "start_time": datetime.fromtimestamp(
64
- psutil.Process(os.getpid()).create_time()
65
- ).strftime("%Y-%m-%d %H:%M:%S"),
66
- "threads": self.get_threads_info(),
67
- "subprocesses": self.get_subprocesses_info(),
68
- "active_connections": self.get_active_connections(),
69
- }
70
- return process_info
71
-
72
- def get_threads_info(self) -> List[Dict[str, Union[int, str]]]:
73
- """
74
- Retrieves information about the threads running in the ProxyServer.
75
-
76
- Returns:
77
- list: A list of dictionaries, each containing information
78
- about a thread.
79
- """
80
- threads_info = []
81
- for thread in threading.enumerate():
82
- threads_info.append(
83
- {
84
- "thread_id": thread.ident,
85
- "name": thread.name,
86
- "status": self.get_thread_status(thread),
87
- }
88
- )
89
- return threads_info
90
-
91
- def get_thread_status(self, thread: threading.Thread) -> str:
92
- """
93
- Gets the status of a given thread.
94
-
95
- Args:
96
- thread (threading.Thread): The thread whose status is to be retrieved.
97
-
98
- Returns:
99
- str: The status of the thread ('running', 'terminated', or 'unknown').
100
- """
101
- try:
102
- if thread.is_alive():
103
- return "running"
104
- return "terminated"
105
- except AttributeError:
106
- return "unknown"
107
-
108
- def get_subprocesses_info(
109
- self,
110
- ) -> Dict[str, Dict[str, Union[str, List[Dict[str, Union[int, str]]]]]]:
111
- """
112
- Retrieves the status of the ProxyServer's subprocesses, including
113
- filtering, shortcuts, cancel inspection, and custom header processes.
114
-
115
- Returns:
116
- dict: A dictionary containing subprocess statuses.
117
- """
118
- subprocesses_info = {}
119
-
120
- subprocesses = {
121
- "filter": self.proxy_server.filter_proc,
122
- "shortcuts": self.proxy_server.shortcuts_proc,
123
- "cancel_inspect": self.proxy_server.cancel_inspect_proc,
124
- "custom_header": self.proxy_server.custom_header_proc,
125
- }
126
-
127
- for name, process in subprocesses.items():
128
- if process is not None and process.is_alive():
129
- subprocesses_info[name] = self.get_subprocess_status(process, name)
130
- return subprocesses_info
131
-
132
- def get_subprocess_status(
133
- self, process: multiprocessing.Process, name: str
134
- ) -> Dict[str, Union[str, None, List[Dict[str, Union[int, str]]]]]:
135
- """
136
- Retrieves the status of a subprocess.
137
-
138
- Args:
139
- process (multiprocessing.Process): The subprocess to check.
140
- name (str): The name of the subprocess.
141
-
142
- Returns:
143
- dict: A dictionary containing the subprocess status.
144
- """
145
- if process is None:
146
- return {"status": "not started", "name": name, "threads": []}
147
- try:
148
- status = "running" if process.is_alive() else "terminated"
149
- threads_info = self.get_subprocess_threads_info(process)
150
- except AttributeError:
151
- status = "terminated"
152
- threads_info = []
153
- return {
154
- "pid": process.pid if hasattr(process, "pid") else None,
155
- "status": status,
156
- "name": name,
157
- "threads": threads_info,
158
- }
159
-
160
- def get_subprocess_threads_info(
161
- self, process: multiprocessing.Process
162
- ) -> List[Dict[str, Union[int, str]]]:
163
- """
164
- Retrieves the threads associated with a subprocess.
165
-
166
- Args:
167
- process (multiprocessing.Process): The subprocess to check.
168
-
169
- Returns:
170
- list: A list of dictionaries containing thread information.
171
- """
172
- threads_info = []
173
- try:
174
- for proc_thread in psutil.Process(process.pid).threads():
175
- threads_info.append(
176
- {
177
- "thread_id": proc_thread.id,
178
- "name": f"Thread-{proc_thread.id}",
179
- "status": self.get_thread_status_by_pid(proc_thread.id),
180
- }
181
- )
182
- except (psutil.NoSuchProcess, psutil.AccessDenied):
183
- pass
184
- return threads_info
185
-
186
- def get_thread_status_by_pid(self, thread_id: int) -> str:
187
- """
188
- Attempts to retrieve the status of a thread by its PID.
189
-
190
- Args:
191
- thread_id (int): The thread's ID.
192
-
193
- Returns:
194
- str: The status of the thread ('running' or 'terminated').
195
- """
196
- try:
197
- process = psutil.Process(thread_id)
198
- if process.is_running():
199
- return "running"
200
- return "terminated"
201
- except psutil.NoSuchProcess:
202
- return "terminated"
203
-
204
- def get_active_connections(self) -> List[Dict[str, Union[int, Dict]]]:
205
- """
206
- Retrieves information about the active client connections to the ProxyServer.
207
-
208
- Returns:
209
- list: A list of dictionaries containing information about active connections.
210
- """
211
- return [
212
- {"thread_id": thread_id, **conn}
213
- for thread_id, conn in self.proxy_server.active_connections.items()
214
- ]
215
-
216
- auth = HTTPBasicAuth()
217
-
218
- users = {"admin": generate_password_hash(flask_pass)}
219
-
220
- @auth.verify_password
221
- def verify_password(username, password):
222
- if username in users and check_password_hash(users.get(username), password):
223
- return username
224
- return None
225
-
226
- app = Flask(__name__, static_folder="static")
227
- if not debug:
228
- log = logging.getLogger("werkzeug")
229
- log.setLevel(logging.ERROR)
230
-
231
- @app.route("/")
232
- @auth.login_required
233
- def index():
234
- """
235
- Renders the index page for the Flask application.
236
-
237
- Returns:
238
- str: The rendered HTML content of the index page.
239
- """
240
- return render_template("index.html")
241
-
242
- @app.route("/monitoring", methods=["GET"])
243
- @auth.login_required
244
- def monitoring():
245
- """
246
- Returns the monitoring data for the ProxyServer in JSON format.
247
-
248
- Returns:
249
- Response: A JSON response containing the server's process information.
250
- """
251
- monitor = ProxyMonitor(proxy_server)
252
- return jsonify(monitor.get_process_info())
253
-
254
- @app.route("/config", methods=["GET"])
255
- @auth.login_required
256
- def config():
257
- config_data = {
258
- "host": proxy_server.host_port[0],
259
- "port": proxy_server.host_port[1],
260
- "debug": proxy_server.debug,
261
- "html_403": proxy_server.html_403,
262
- "logger_config": (
263
- proxy_server.logger_config.to_dict()
264
- if proxy_server.logger_config
265
- else None
266
- ),
267
- "filter_config": (
268
- proxy_server.filter_config.to_dict()
269
- if proxy_server.filter_config
270
- else None
271
- ),
272
- "ssl_config": (
273
- proxy_server.ssl_config.to_dict() if proxy_server.ssl_config else None
274
- ),
275
- "flask_port": proxy_server.monitoring_config.flask_port,
276
- }
277
- return jsonify(config_data)
278
-
279
- app.run(host="0.0.0.0", port=flask_port) # nosec
File without changes
File without changes
File without changes
File without changes
File without changes