pyproxytools 0.3.2__py3-none-any.whl

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.
benchmark/benchmark.py ADDED
@@ -0,0 +1,165 @@
1
+ """
2
+ This module provides a set of functions to benchmark the performance of a proxy server
3
+ by comparing the response times for HTTP requests sent with and without the use of a proxy.
4
+ """
5
+
6
+ import time
7
+ import argparse
8
+ import sys
9
+ import os
10
+ from datetime import datetime
11
+ import pandas as pd
12
+ from utils.req import send_request_with_proxy, send_request_without_proxy
13
+ from utils.html import create_combined_html_report
14
+
15
+
16
+ def benchmark(url: str, proxy: str, num_requests: int) -> tuple:
17
+ """
18
+ Benchmarks the performance of sending requests to the specified$
19
+ URL with and without using a proxy. It sends multiple requests and
20
+ records the time taken for each.
21
+
22
+ Args:
23
+ url (str): The URL to benchmark.
24
+ proxy (str): The proxy URL to use for the benchmark.
25
+ num_requests (int): The number of requests to send.
26
+
27
+ Returns:
28
+ tuple: A tuple containing:
29
+ - A dictionary with statistics (average, min, max) for requests without and with proxy.
30
+ - A pandas DataFrame containing the times for each request without and with proxy.
31
+ """
32
+ times_without_proxy = []
33
+ times_with_proxy = []
34
+
35
+ print(f"Sending requests without proxy for {url}...")
36
+ for i in range(num_requests):
37
+ times_without_proxy.append(send_request_without_proxy(url))
38
+ sys.stdout.write(f"\rRequests sent without proxy: {i + 1}/{num_requests}")
39
+ sys.stdout.flush()
40
+ time.sleep(0.1)
41
+
42
+ print(f"\nSending requests with proxy for {url}...")
43
+ for i in range(num_requests):
44
+ times_with_proxy.append(send_request_with_proxy(url, proxy))
45
+ sys.stdout.write(f"\rRequests sent with proxy: {i + 1}/{num_requests}")
46
+ sys.stdout.flush()
47
+ time.sleep(0.1)
48
+
49
+ print("\n")
50
+
51
+ stats = {
52
+ "avg_without_proxy": sum(times_without_proxy) / len(times_without_proxy),
53
+ "min_without_proxy": min(times_without_proxy),
54
+ "max_without_proxy": max(times_without_proxy),
55
+ "avg_with_proxy": sum(times_with_proxy) / len(times_with_proxy),
56
+ "min_with_proxy": min(times_with_proxy),
57
+ "max_with_proxy": max(times_with_proxy),
58
+ }
59
+
60
+ results = pd.DataFrame(
61
+ {
62
+ "Request Number": range(1, num_requests + 1),
63
+ "Without Proxy": times_without_proxy,
64
+ "With Proxy": times_with_proxy,
65
+ }
66
+ )
67
+
68
+ return stats, results
69
+
70
+
71
+ def main() -> None:
72
+ """
73
+ Main function to parse command-line arguments, run benchmarks, and generate the report.
74
+ It either benchmarks a single URL or a list of URLs from a file.
75
+
76
+ Returns:
77
+ None
78
+ """
79
+ parser = argparse.ArgumentParser(description="Proxy performance benchmark.")
80
+ parser.add_argument(
81
+ "--proxy-url",
82
+ type=str,
83
+ default="http://localhost:8080",
84
+ help="The proxy URL to use",
85
+ )
86
+ parser.add_argument(
87
+ "--target-url",
88
+ type=str,
89
+ help="A single URL to test (e.g., http://example.com)",
90
+ )
91
+ parser.add_argument(
92
+ "--target-file",
93
+ type=str,
94
+ help="A file containing a list of URLs to test",
95
+ )
96
+ parser.add_argument(
97
+ "--num-requests",
98
+ type=int,
99
+ default=10,
100
+ help="Number of requests to send (default: 10)",
101
+ )
102
+ parser.add_argument(
103
+ "--output-dir", type=str, default="benchmark/outputs", help="Output directory"
104
+ )
105
+ args = parser.parse_args()
106
+
107
+ if not args.target_url and not args.target_file:
108
+ print("Error: you must provide either --target-url or --target-file.")
109
+ sys.exit(1)
110
+
111
+ if not os.path.exists(args.output_dir):
112
+ os.makedirs(args.output_dir)
113
+
114
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
115
+ all_results = {}
116
+
117
+ if args.target_file:
118
+ if not os.path.exists(args.target_file):
119
+ print(f"Error: the file {args.target_file} does not exist.")
120
+ sys.exit(1)
121
+
122
+ with open(args.target_file, "r", encoding="utf-8") as f:
123
+ urls = [line.strip() for line in f if line.strip()]
124
+
125
+ for url in urls:
126
+ print(f"\nBenchmarking for {url}")
127
+ stats, results = benchmark(url, args.proxy_url, args.num_requests)
128
+ all_results[url] = (stats, results)
129
+ else:
130
+ stats, results = benchmark(args.target_url, args.proxy_url, args.num_requests)
131
+ all_results[args.target_url] = (stats, results)
132
+
133
+ avg_without_proxy_list = []
134
+ avg_with_proxy_list = []
135
+
136
+ for stats, _ in all_results.values():
137
+ avg_without_proxy_list.append(stats["avg_without_proxy"])
138
+ avg_with_proxy_list.append(stats["avg_with_proxy"])
139
+
140
+ global_avg_without_proxy = sum(avg_without_proxy_list) / len(avg_without_proxy_list)
141
+ global_avg_with_proxy = sum(avg_with_proxy_list) / len(avg_with_proxy_list)
142
+
143
+ percentage_change = (
144
+ (global_avg_with_proxy - global_avg_without_proxy) / global_avg_without_proxy
145
+ ) * 100
146
+
147
+ print(f"Global average without proxy: {global_avg_without_proxy:.6f} seconds")
148
+ print(f"Global average with proxy: {global_avg_with_proxy:.6f} seconds")
149
+ print(
150
+ f"Impact: {'Improvement' if percentage_change < 0 else 'Slowdown'} of "
151
+ f"{abs(percentage_change):.2f}%"
152
+ )
153
+
154
+ create_combined_html_report(
155
+ all_results,
156
+ global_avg_without_proxy,
157
+ global_avg_with_proxy,
158
+ percentage_change,
159
+ args.output_dir,
160
+ timestamp,
161
+ )
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
File without changes
@@ -0,0 +1,179 @@
1
+ """
2
+ This module provides functions for generating HTML reports to visualize
3
+ benchmark results comparing performance with and without a proxy.
4
+ """
5
+
6
+ import os
7
+ import plotly.graph_objects as go
8
+
9
+ TEMPLATE_PATH = "benchmark/templates/report_template.html"
10
+
11
+
12
+ def generate_combined_table(all_results: dict) -> str:
13
+ """
14
+ Generates a single HTML table combining statistics for all
15
+ URLs with sub-columns for avg, min, and max.
16
+
17
+ Args:
18
+ all_results (dict): A dictionary containing the results for each URL.
19
+
20
+ Returns:
21
+ str: The HTML table as a string.
22
+ """
23
+ table_html = """
24
+ <div class="summary">
25
+ <h2>Benchmark Results Summary</h2>
26
+ <table>
27
+ <thead>
28
+ <tr>
29
+ <th>URL</th>
30
+ <th colspan="3">Without Proxy</th>
31
+ <th colspan="3">With Proxy</th>
32
+ </tr>
33
+ <tr>
34
+ <th></th>
35
+ <th>Avg (s)</th>
36
+ <th>Min (s)</th>
37
+ <th>Max (s)</th>
38
+ <th>Avg (s)</th>
39
+ <th>Min (s)</th>
40
+ <th>Max (s)</th>
41
+ </tr>
42
+ </thead>
43
+ <tbody>
44
+ """
45
+
46
+ for url, (stats, _) in all_results.items():
47
+ table_html += f"""
48
+ <tr>
49
+ <td>{url}</td>
50
+ <td>{stats['avg_without_proxy']:.5f}</td>
51
+ <td>{stats['min_without_proxy']:.5f}</td>
52
+ <td>{stats['max_without_proxy']:.5f}</td>
53
+ <td>{stats['avg_with_proxy']:.5f}</td>
54
+ <td>{stats['min_with_proxy']:.5f}</td>
55
+ <td>{stats['max_with_proxy']:.5f}</td>
56
+ </tr>
57
+ """
58
+
59
+ table_html += """
60
+ </tbody>
61
+ </table>
62
+ </div>
63
+ <hr>
64
+ """
65
+
66
+ return table_html
67
+
68
+
69
+ def prepare_filenames(output_dir: str, timestamp: str) -> dict:
70
+ """
71
+ Prepares the filenames for the report and plotly files.
72
+
73
+ Args:
74
+ output_dir (str): The directory to save the report in.
75
+ timestamp (str): The timestamp to use in filenames.
76
+
77
+ Returns:
78
+ dict: A dictionary containing the plotly and html file paths.
79
+ """
80
+ output_dir = os.path.normpath(output_dir)
81
+
82
+ plotly_filename = f"benchmark_combined_interactive_{timestamp}.html"
83
+ html_filename = f"benchmark_combined_report_{timestamp}.html"
84
+
85
+ plotly_filepath = os.path.join(output_dir, plotly_filename)
86
+ html_filepath = os.path.join(output_dir, html_filename)
87
+
88
+ return {"plotly": plotly_filepath, "html": html_filepath}
89
+
90
+
91
+ def render_template(template_path: str, context: dict) -> str:
92
+ """
93
+ Renders an HTML template by replacing placeholders with provided context.
94
+
95
+ Args:
96
+ template_path (str): Path to the HTML template.
97
+ context (dict): A dictionary with keys matching placeholders.
98
+
99
+ Returns:
100
+ str: The rendered HTML content.
101
+ """
102
+ with open(template_path, "r", encoding="utf-8") as f:
103
+ template = f.read()
104
+ return template.format(**context)
105
+
106
+
107
+ def create_combined_html_report(
108
+ all_results: dict,
109
+ avg_without_proxy: float,
110
+ avg_with_proxy: float,
111
+ percentage_change: float,
112
+ output_dir: str,
113
+ timestamp: str,
114
+ ) -> None:
115
+ """
116
+ Generates an HTML report with the benchmark results, including graphs and statistics.
117
+ Saves the report to the specified output directory.
118
+
119
+ Args:
120
+ all_results (dict): A dictionary containing the results for each URL.
121
+ avg_without_proxy (float): The average time for requests without a proxy.
122
+ avg_with_proxy (float): The average time for requests with a proxy.
123
+ percentage_change (float): The percentage change in performance
124
+ between requests with and without a proxy.
125
+ output_dir (str): The directory to save the report in.
126
+ timestamp (str): The timestamp to use in filenames.
127
+
128
+ Returns:
129
+ None
130
+ """
131
+ fig = go.Figure()
132
+
133
+ filenames = prepare_filenames(output_dir, timestamp)
134
+
135
+ for url, (_, results) in all_results.items():
136
+ fig.add_trace(
137
+ go.Scatter(
138
+ x=results["Request Number"],
139
+ y=results["Without Proxy"],
140
+ mode="lines+markers",
141
+ name=f"Without Proxy - {url}",
142
+ )
143
+ )
144
+ fig.add_trace(
145
+ go.Scatter(
146
+ x=results["Request Number"],
147
+ y=results["With Proxy"],
148
+ mode="lines+markers",
149
+ name=f"With Proxy - {url}",
150
+ )
151
+ )
152
+
153
+ fig.update_layout(
154
+ title="Response Time per Request (All URLs)",
155
+ xaxis_title="Request Number",
156
+ yaxis_title="Response Time (seconds)",
157
+ )
158
+
159
+ fig.write_html(filenames["plotly"])
160
+
161
+ html_sections = generate_combined_table(all_results)
162
+
163
+ context = {
164
+ "avg_without_proxy": f"{avg_without_proxy:.6f} seconds",
165
+ "avg_with_proxy": f"{avg_with_proxy:.6f} seconds",
166
+ "impact": (
167
+ f"{'Improvement' if percentage_change < 0 else 'Slowdown'} "
168
+ f"of {abs(percentage_change):.2f}%"
169
+ ),
170
+ "html_sections": html_sections,
171
+ "plotly_filename": os.path.basename(filenames["plotly"]),
172
+ }
173
+
174
+ html_content = render_template(TEMPLATE_PATH, context)
175
+
176
+ with open(filenames["html"], "w", encoding="utf-8") as f:
177
+ f.write(html_content)
178
+
179
+ print(f"\nThe combined report has been generated at '{filenames['html']}'.")
benchmark/utils/req.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ Module for sending HTTP GET requests with and without a proxy,
3
+ and measuring the request completion time.
4
+ """
5
+
6
+ import time
7
+ import requests
8
+
9
+
10
+ def send_request_without_proxy(url: str) -> float:
11
+ """
12
+ Sends an HTTP GET request to the provided URL without using a proxy,
13
+ and measures the time it takes to complete the request.
14
+
15
+ Args:
16
+ url (str): The URL to send the request to.
17
+
18
+ Returns:
19
+ float: The time taken to complete the request in seconds.
20
+ """
21
+ start_time = time.time()
22
+ requests.get(url, timeout=10)
23
+ end_time = time.time()
24
+ return end_time - start_time
25
+
26
+
27
+ def send_request_with_proxy(url: str, proxy: str) -> float:
28
+ """
29
+ Sends an HTTP GET request to the provided URL using a proxy,
30
+ and measures the time it takes to complete the request.
31
+
32
+ Args:
33
+ url (str): The URL to send the request to.
34
+ proxy (str): The proxy URL to use for the request.
35
+
36
+ Returns:
37
+ float: The time taken to complete the request in seconds.
38
+ """
39
+ proxies = {"http": proxy, "https": proxy}
40
+ start_time = time.time()
41
+ requests.get(url, proxies=proxies, timeout=10)
42
+ end_time = time.time()
43
+ return end_time - start_time
pyproxy/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """
2
+ This module defines the version of the application. It contains a single constant
3
+ that holds the current version number of the application.
4
+ """
5
+
6
+ import os
7
+
8
+ __version__ = "0.3.2"
9
+
10
+ if os.path.isdir("pyproxy/monitoring"):
11
+ __slim__ = False
12
+ else:
13
+ __slim__ = True
File without changes
@@ -0,0 +1,126 @@
1
+ """
2
+ pyproxy.handlers.client.py
3
+
4
+ This module defines the ProxyHandlers class used by the proxy server to process
5
+ HTTP and HTTPS client connections. It handles request forwarding, blocking, shortcut
6
+ redirection, custom headers, and optional SSL inspection.
7
+ """
8
+
9
+ import threading
10
+
11
+ from pyproxy.handlers.http import HttpHandler
12
+ from pyproxy.handlers.https import HttpsHandler
13
+
14
+
15
+ class ProxyHandlers:
16
+ """
17
+ ProxyHandlers manages client connections for a proxy server, handling both HTTP
18
+ and HTTPS requests. It processes request forwarding, blocking, SSL inspection,
19
+ and custom headers based on configuration settings. This class is responsible
20
+ for dispatching the correct handler for HTTP or HTTPS requests and managing
21
+ connection-related operations.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ html_403,
27
+ logger_config,
28
+ filter_config,
29
+ ssl_config,
30
+ filter_queue,
31
+ filter_result_queue,
32
+ shortcuts_queue,
33
+ shortcuts_result_queue,
34
+ cancel_inspect_queue,
35
+ cancel_inspect_result_queue,
36
+ custom_header_queue,
37
+ custom_header_result_queue,
38
+ console_logger,
39
+ shortcuts,
40
+ custom_header,
41
+ active_connections,
42
+ proxy_enable,
43
+ proxy_host,
44
+ proxy_port,
45
+ ):
46
+ self.html_403 = html_403
47
+ self.logger_config = logger_config
48
+ self.filter_config = filter_config
49
+ self.ssl_config = ssl_config
50
+ self.filter_queue = filter_queue
51
+ self.filter_result_queue = filter_result_queue
52
+ self.shortcuts_queue = shortcuts_queue
53
+ self.shortcuts_result_queue = shortcuts_result_queue
54
+ self.cancel_inspect_queue = cancel_inspect_queue
55
+ self.cancel_inspect_result_queue = cancel_inspect_result_queue
56
+ self.custom_header_queue = custom_header_queue
57
+ self.custom_header_result_queue = custom_header_result_queue
58
+ self.console_logger = console_logger
59
+ self.config_shortcuts = shortcuts
60
+ self.config_custom_header = custom_header
61
+ self.proxy_enable = proxy_enable
62
+ self.proxy_host = proxy_host
63
+ self.proxy_port = proxy_port
64
+ self.active_connections = active_connections
65
+
66
+ def handle_client(self, client_socket):
67
+ """
68
+ Handles an incoming client connection by processing the request and forwarding
69
+ it to the appropriate handler based on whether the request is HTTP or HTTPS.
70
+
71
+ Args:
72
+ client_socket (socket): The socket object for the client connection.
73
+ """
74
+ request = client_socket.recv(4096)
75
+
76
+ if not request:
77
+ self.console_logger.debug("No request received, closing connection.")
78
+ client_socket.close()
79
+ self.active_connections.pop(threading.get_ident(), None)
80
+ return
81
+
82
+ first_line = request.decode(errors="ignore").split("\n")[0]
83
+
84
+ if first_line.startswith("CONNECT"):
85
+ client_https_handler = HttpsHandler(
86
+ html_403=self.html_403,
87
+ logger_config=self.logger_config,
88
+ filter_config=self.filter_config,
89
+ ssl_config=self.ssl_config,
90
+ filter_queue=self.filter_queue,
91
+ filter_result_queue=self.filter_result_queue,
92
+ shortcuts_queue=self.shortcuts_queue,
93
+ shortcuts_result_queue=self.shortcuts_result_queue,
94
+ cancel_inspect_queue=self.cancel_inspect_queue,
95
+ cancel_inspect_result_queue=self.cancel_inspect_result_queue,
96
+ custom_header_queue=self.custom_header_queue,
97
+ custom_header_result_queue=self.custom_header_result_queue,
98
+ console_logger=self.console_logger,
99
+ shortcuts=self.config_shortcuts,
100
+ custom_header=self.config_custom_header,
101
+ proxy_enable=self.proxy_enable,
102
+ proxy_host=self.proxy_host,
103
+ proxy_port=self.proxy_port,
104
+ active_connections=self.active_connections,
105
+ )
106
+ client_https_handler.handle_https_connection(client_socket, first_line)
107
+ else:
108
+ client_http_handler = HttpHandler(
109
+ html_403=self.html_403,
110
+ logger_config=self.logger_config,
111
+ filter_config=self.filter_config,
112
+ filter_queue=self.filter_queue,
113
+ filter_result_queue=self.filter_result_queue,
114
+ shortcuts_queue=self.shortcuts_queue,
115
+ shortcuts_result_queue=self.shortcuts_result_queue,
116
+ custom_header_queue=self.custom_header_queue,
117
+ custom_header_result_queue=self.custom_header_result_queue,
118
+ console_logger=self.console_logger,
119
+ shortcuts=self.config_shortcuts,
120
+ custom_header=self.config_custom_header,
121
+ proxy_enable=self.proxy_enable,
122
+ proxy_host=self.proxy_host,
123
+ proxy_port=self.proxy_port,
124
+ active_connections=self.active_connections,
125
+ )
126
+ client_http_handler.handle_http_request(client_socket, request)