pyproxytools 0.4.2__tar.gz → 0.4.4__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 (47) hide show
  1. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/LICENSE +1 -1
  2. {pyproxytools-0.4.2/pyproxytools.egg-info → pyproxytools-0.4.4}/PKG-INFO +23 -17
  3. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/README.md +19 -13
  4. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproject.toml +3 -3
  5. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/__init__.py +1 -1
  6. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/handlers/http.py +40 -16
  7. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/handlers/https.py +81 -22
  8. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/monitoring/web.py +1 -1
  9. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/pyproxy.py +40 -3
  10. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/server.py +8 -2
  11. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/utils/config.py +2 -0
  12. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/utils/logger.py +24 -2
  13. {pyproxytools-0.4.2 → pyproxytools-0.4.4/pyproxytools.egg-info}/PKG-INFO +23 -17
  14. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/utils/test_logger.py +15 -6
  15. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/benchmark/benchmark.py +0 -0
  16. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/benchmark/utils/__init__.py +0 -0
  17. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/benchmark/utils/html.py +0 -0
  18. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/benchmark/utils/req.py +0 -0
  19. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/__main__.py +0 -0
  20. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/handlers/__init__.py +0 -0
  21. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/handlers/client.py +0 -0
  22. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/modules/__init__.py +0 -0
  23. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/modules/cancel_inspect.py +0 -0
  24. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/modules/custom_header.py +0 -0
  25. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/modules/filter.py +0 -0
  26. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/modules/shortcuts.py +0 -0
  27. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/monitoring/__init__.py +0 -0
  28. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/utils/__init__.py +0 -0
  29. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/utils/args.py +0 -0
  30. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/utils/crypto.py +0 -0
  31. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxy/utils/http_req.py +0 -0
  32. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxytools.egg-info/SOURCES.txt +0 -0
  33. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxytools.egg-info/dependency_links.txt +0 -0
  34. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxytools.egg-info/entry_points.txt +0 -0
  35. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxytools.egg-info/requires.txt +0 -0
  36. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/pyproxytools.egg-info/top_level.txt +0 -0
  37. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/requirements.txt +0 -0
  38. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/setup.cfg +0 -0
  39. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/setup.py +0 -0
  40. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/modules/__init__.py +0 -0
  41. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/modules/test_cancel_inspect.py +0 -0
  42. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/modules/test_custom_header.py +0 -0
  43. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/modules/test_filter.py +0 -0
  44. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/modules/test_shortcuts.py +0 -0
  45. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/utils/__init__.py +0 -0
  46. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/utils/test_crypto.py +0 -0
  47. {pyproxytools-0.4.2 → pyproxytools-0.4.4}/tests/utils/test_http_req.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 6C656C65
3
+ Copyright (c) 2025 pyproxytools
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyproxytools
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Lightweight and fast python web proxy
5
- Author: 6C656C65
5
+ Author: pyproxytools
6
6
  License-Expression: MIT
7
- Project-URL: Documentation, https://github.com/6C656C65/pyproxy/wiki
8
- Project-URL: Issue tracker, https://github.com/6C656C65/pyproxy/issues
7
+ Project-URL: Documentation, https://github.com/pyproxytools/pyproxy/wiki
8
+ Project-URL: Issue tracker, https://github.com/pyproxytools/pyproxy/issues
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Natural Language :: English
@@ -39,14 +39,14 @@ Dynamic: license-file
39
39
  **pyproxy** is a lightweight, fast, and customizable Python-based web proxy server designed to handle both HTTP and HTTPS traffic efficiently. It can be used for various purposes, including web scraping, traffic monitoring, and content filtering.
40
40
 
41
41
  <p align="center">
42
- <img src="https://img.shields.io/github/license/6C656C65/pyproxy?style=for-the-badge">
43
- <img src="https://img.shields.io/github/issues/6C656C65/pyproxy?style=for-the-badge">
44
- <img src="https://img.shields.io/github/issues-closed/6C656C65/pyproxy?style=for-the-badge">
42
+ <img src="https://img.shields.io/github/license/pyproxytools/pyproxy?style=for-the-badge">
43
+ <img src="https://img.shields.io/github/issues/pyproxytools/pyproxy?style=for-the-badge">
44
+ <img src="https://img.shields.io/github/issues-closed/pyproxytools/pyproxy?style=for-the-badge">
45
45
  <br>
46
- <img src="https://img.shields.io/github/forks/6C656C65/pyproxy?style=for-the-badge">
47
- <img src="https://img.shields.io/github/stars/6C656C65/pyproxy?style=for-the-badge">
48
- <img src="https://img.shields.io/github/commit-activity/w/6C656C65/pyproxy?style=for-the-badge">
49
- <img src="https://img.shields.io/github/contributors/6C656C65/pyproxy?style=for-the-badge">
46
+ <img src="https://img.shields.io/github/forks/pyproxytools/pyproxy?style=for-the-badge">
47
+ <img src="https://img.shields.io/github/stars/pyproxytools/pyproxy?style=for-the-badge">
48
+ <img src="https://img.shields.io/github/commit-activity/w/pyproxytools/pyproxy?style=for-the-badge">
49
+ <img src="https://img.shields.io/github/contributors/pyproxytools/pyproxy?style=for-the-badge">
50
50
  <br>
51
51
  <img src="https://img.shields.io/pypi/v/pyproxytools?style=for-the-badge">
52
52
  <img src="https://img.shields.io/pypi/pyversions/pyproxytools?style=for-the-badge">
@@ -81,18 +81,24 @@ pip install pyproxytools
81
81
 
82
82
  ### Install from source
83
83
  ```bash
84
- git clone https://github.com/6C656C65/pyproxy.git
84
+ git clone https://github.com/pyproxytools/pyproxy.git
85
85
  cd pyproxy
86
86
  pip install -r requirements.txt
87
87
  ```
88
88
 
89
89
  ### Install with Docker
90
90
  ```bash
91
- docker pull ghcr.io/6c656c65/pyproxy:latest
92
- docker run -d ghcr.io/6c656c65/pyproxy:latest
91
+ docker pull ghcr.io/pyproxytools/pyproxy:latest
92
+ docker run -d ghcr.io/pyproxytools/pyproxy:latest
93
93
  ```
94
94
  You can use slim images by adding `-slim` to the end of the tags
95
95
 
96
+ ### Install with Compose
97
+ ```bash
98
+ wget https://raw.githubusercontent.com/pyproxytools/pyproxy/main/docker-compose.yml
99
+ docker-compose up -d
100
+ ```
101
+
96
102
  ## 🚀 **Usage**
97
103
 
98
104
  ### Start the proxy
@@ -103,7 +109,7 @@ The proxy will be available at: `0.0.0.0:8080`.
103
109
  The access log will be available at `./logs/access.log`.
104
110
 
105
111
  ## 📚 **Documentation**
106
- If you encounter any problems, or if you want to use the program in a particular way, I advise you to read the [documentation](https://github.com/6C656C65/pyproxy/wiki).
112
+ If you encounter any problems, or if you want to use the program in a particular way, I advise you to read the [documentation](https://github.com/pyproxytools/pyproxy/wiki).
107
113
 
108
114
  ## 🔧 **To do**
109
115
 
@@ -124,12 +130,12 @@ Contributions are welcome and appreciated! If you'd like to improve this project
124
130
 
125
131
  ## 📦 Deployment with Ansible
126
132
 
127
- If you want to deploy **pyproxy** automatically to remote servers (via source or Docker), an official [Ansible role](https://github.com/6C656C65/pyproxy_ansible) is available:
133
+ If you want to deploy **pyproxy** automatically to remote servers (via source or Docker), an official [Ansible role](https://github.com/pyproxytools/pyproxy-ansible) is available:
128
134
 
129
135
  * 🔧 Install from source or run as a Docker container
130
136
  * 📁 Supports customization of ports, versions, and paths
131
137
  * 🚀 Easily integrable into your infrastructure or CI/CD pipelines
132
138
 
133
- 👉 Check out the [ansible-role-pyproxy](https://github.com/6C656C65/pyproxy_ansible) repository for more details and usage instructions.
139
+ 👉 Check out the [pyproxy-ansible](https://github.com/pyproxytools/pyproxy-ansible) repository for more details and usage instructions.
134
140
 
135
141
  ---
@@ -6,14 +6,14 @@
6
6
  **pyproxy** is a lightweight, fast, and customizable Python-based web proxy server designed to handle both HTTP and HTTPS traffic efficiently. It can be used for various purposes, including web scraping, traffic monitoring, and content filtering.
7
7
 
8
8
  <p align="center">
9
- <img src="https://img.shields.io/github/license/6C656C65/pyproxy?style=for-the-badge">
10
- <img src="https://img.shields.io/github/issues/6C656C65/pyproxy?style=for-the-badge">
11
- <img src="https://img.shields.io/github/issues-closed/6C656C65/pyproxy?style=for-the-badge">
9
+ <img src="https://img.shields.io/github/license/pyproxytools/pyproxy?style=for-the-badge">
10
+ <img src="https://img.shields.io/github/issues/pyproxytools/pyproxy?style=for-the-badge">
11
+ <img src="https://img.shields.io/github/issues-closed/pyproxytools/pyproxy?style=for-the-badge">
12
12
  <br>
13
- <img src="https://img.shields.io/github/forks/6C656C65/pyproxy?style=for-the-badge">
14
- <img src="https://img.shields.io/github/stars/6C656C65/pyproxy?style=for-the-badge">
15
- <img src="https://img.shields.io/github/commit-activity/w/6C656C65/pyproxy?style=for-the-badge">
16
- <img src="https://img.shields.io/github/contributors/6C656C65/pyproxy?style=for-the-badge">
13
+ <img src="https://img.shields.io/github/forks/pyproxytools/pyproxy?style=for-the-badge">
14
+ <img src="https://img.shields.io/github/stars/pyproxytools/pyproxy?style=for-the-badge">
15
+ <img src="https://img.shields.io/github/commit-activity/w/pyproxytools/pyproxy?style=for-the-badge">
16
+ <img src="https://img.shields.io/github/contributors/pyproxytools/pyproxy?style=for-the-badge">
17
17
  <br>
18
18
  <img src="https://img.shields.io/pypi/v/pyproxytools?style=for-the-badge">
19
19
  <img src="https://img.shields.io/pypi/pyversions/pyproxytools?style=for-the-badge">
@@ -48,18 +48,24 @@ pip install pyproxytools
48
48
 
49
49
  ### Install from source
50
50
  ```bash
51
- git clone https://github.com/6C656C65/pyproxy.git
51
+ git clone https://github.com/pyproxytools/pyproxy.git
52
52
  cd pyproxy
53
53
  pip install -r requirements.txt
54
54
  ```
55
55
 
56
56
  ### Install with Docker
57
57
  ```bash
58
- docker pull ghcr.io/6c656c65/pyproxy:latest
59
- docker run -d ghcr.io/6c656c65/pyproxy:latest
58
+ docker pull ghcr.io/pyproxytools/pyproxy:latest
59
+ docker run -d ghcr.io/pyproxytools/pyproxy:latest
60
60
  ```
61
61
  You can use slim images by adding `-slim` to the end of the tags
62
62
 
63
+ ### Install with Compose
64
+ ```bash
65
+ wget https://raw.githubusercontent.com/pyproxytools/pyproxy/main/docker-compose.yml
66
+ docker-compose up -d
67
+ ```
68
+
63
69
  ## 🚀 **Usage**
64
70
 
65
71
  ### Start the proxy
@@ -70,7 +76,7 @@ The proxy will be available at: `0.0.0.0:8080`.
70
76
  The access log will be available at `./logs/access.log`.
71
77
 
72
78
  ## 📚 **Documentation**
73
- If you encounter any problems, or if you want to use the program in a particular way, I advise you to read the [documentation](https://github.com/6C656C65/pyproxy/wiki).
79
+ If you encounter any problems, or if you want to use the program in a particular way, I advise you to read the [documentation](https://github.com/pyproxytools/pyproxy/wiki).
74
80
 
75
81
  ## 🔧 **To do**
76
82
 
@@ -91,12 +97,12 @@ Contributions are welcome and appreciated! If you'd like to improve this project
91
97
 
92
98
  ## 📦 Deployment with Ansible
93
99
 
94
- If you want to deploy **pyproxy** automatically to remote servers (via source or Docker), an official [Ansible role](https://github.com/6C656C65/pyproxy_ansible) is available:
100
+ If you want to deploy **pyproxy** automatically to remote servers (via source or Docker), an official [Ansible role](https://github.com/pyproxytools/pyproxy-ansible) is available:
95
101
 
96
102
  * 🔧 Install from source or run as a Docker container
97
103
  * 📁 Supports customization of ports, versions, and paths
98
104
  * 🚀 Easily integrable into your infrastructure or CI/CD pipelines
99
105
 
100
- 👉 Check out the [ansible-role-pyproxy](https://github.com/6C656C65/pyproxy_ansible) repository for more details and usage instructions.
106
+ 👉 Check out the [pyproxy-ansible](https://github.com/pyproxytools/pyproxy-ansible) repository for more details and usage instructions.
101
107
 
102
108
  ---
@@ -6,7 +6,7 @@ license = "MIT"
6
6
  license-files = [
7
7
  "LICENSE",
8
8
  ]
9
- authors = [{name = "6C656C65"}]
9
+ authors = [{name = "pyproxytools"}]
10
10
  classifiers = [
11
11
  "Development Status :: 5 - Production/Stable",
12
12
  "Intended Audience :: Developers",
@@ -26,8 +26,8 @@ classifiers = [
26
26
  dynamic = ["version", "dependencies"]
27
27
 
28
28
  [project.urls]
29
- Documentation = "https://github.com/6C656C65/pyproxy/wiki"
30
- "Issue tracker" = "https://github.com/6C656C65/pyproxy/issues"
29
+ Documentation = "https://github.com/pyproxytools/pyproxy/wiki"
30
+ "Issue tracker" = "https://github.com/pyproxytools/pyproxy/issues"
31
31
 
32
32
  [tool.setuptools.packages]
33
33
  find = {}
@@ -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.2"
8
+ __version__ = "0.4.4"
9
9
 
10
10
  if os.path.isdir("pyproxy/monitoring"):
11
11
  __slim__ = False
@@ -109,8 +109,18 @@ class HttpHandler:
109
109
  Sends an HTTP 403 Forbidden response to the client.
110
110
  """
111
111
  if not self.logger_config.no_logging_block:
112
+ method, domain_port, protocol = first_line.split(" ")
113
+ domain, port = domain_port.split(":")
112
114
  self.logger_config.block_logger.info(
113
- "%s - %s - %s", client_socket.getpeername()[0], url, first_line
115
+ "",
116
+ extra={
117
+ "ip_src": client_socket.getpeername()[0],
118
+ "url": url,
119
+ "method": method,
120
+ "domain": domain,
121
+ "port": port,
122
+ "protocol": protocol,
123
+ },
114
124
  )
115
125
  with open(self.html_403, "r", encoding="utf-8") as f:
116
126
  custom_403_page = f.read()
@@ -155,16 +165,6 @@ class HttpHandler:
155
165
  self._send_403(client_socket, url, first_line)
156
166
  return
157
167
 
158
- parsed_url = urlparse(url)
159
- server_host = parsed_url.hostname
160
- if not self.logger_config.no_logging_access:
161
- self.logger_config.access_logger.info(
162
- "%s - %s - %s",
163
- client_socket.getpeername()[0],
164
- f"http://{server_host}",
165
- first_line,
166
- )
167
-
168
168
  if self.config_custom_header and os.path.isfile(self.config_custom_header):
169
169
  request_text = request.decode(errors="ignore")
170
170
  request_lines = request_text.split("\r\n")
@@ -177,12 +177,14 @@ class HttpHandler:
177
177
  )
178
178
  modified_request = self._rebuild_http_request(request_line, headers, body)
179
179
 
180
- self.forward_request_to_server(client_socket, modified_request, url)
180
+ self.forward_request_to_server(
181
+ client_socket, modified_request, url, first_line
182
+ )
181
183
 
182
184
  else:
183
- self.forward_request_to_server(client_socket, request, url)
185
+ self.forward_request_to_server(client_socket, request, url, first_line)
184
186
 
185
- def forward_request_to_server(self, client_socket, request, url):
187
+ def forward_request_to_server(self, client_socket, request, url, first_line):
186
188
  """
187
189
  Forwards the HTTP request to the target server and sends the response back to the client.
188
190
 
@@ -190,6 +192,7 @@ class HttpHandler:
190
192
  client_socket (socket): The socket object for the client connection.
191
193
  request (bytes): The raw HTTP request sent by the client.
192
194
  url (str): The target URL from the HTTP request.
195
+ first_line (str): The first line of the HTTP request (e.g., "GET / HTTP/1.1").
193
196
  """
194
197
  if self.proxy_config.enable:
195
198
  server_host, server_port = self.proxy_config.host, self.proxy_config.port
@@ -202,8 +205,12 @@ class HttpHandler:
202
205
  thread_id = threading.get_ident()
203
206
 
204
207
  if thread_id in self.active_connections:
205
- self.active_connections[thread_id]["target_ip"] = server_host
206
- self.active_connections[thread_id]["target_port"] = server_port
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
+ }
207
214
 
208
215
  try:
209
216
  server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -238,6 +245,23 @@ class HttpHandler:
238
245
  client_socket.close()
239
246
  self.active_connections.pop(thread_id, None)
240
247
  finally:
248
+ if not self.logger_config.no_logging_access:
249
+ method, url, protocol = first_line.split(" ")
250
+
251
+ conn_data = self.active_connections.get(thread_id, {})
252
+ self.logger_config.access_logger.info(
253
+ "",
254
+ extra={
255
+ "ip_src": client_socket.getpeername()[0],
256
+ "url": f"http://{server_host}",
257
+ "method": method,
258
+ "domain": parsed_url.hostname,
259
+ "port": parsed_url.port,
260
+ "protocol": protocol,
261
+ "bytes_sent": conn_data.get("bytes_sent", 0),
262
+ "bytes_received": conn_data.get("bytes_received", 0),
263
+ },
264
+ )
241
265
  client_socket.close()
242
266
  server_socket.close()
243
267
  self.active_connections.pop(thread_id, None)
@@ -80,8 +80,18 @@ class HttpsHandler:
80
80
  Sends an HTTP 403 Forbidden response to the client.
81
81
  """
82
82
  if not self.logger_config.no_logging_block:
83
+ method, domain_port, protocol = first_line.split(" ")
84
+ domain, port = domain_port.split(":")
83
85
  self.logger_config.block_logger.info(
84
- "%s - %s - %s", client_socket.getpeername()[0], url, first_line
86
+ "",
87
+ extra={
88
+ "ip_src": client_socket.getpeername()[0],
89
+ "url": url,
90
+ "method": method,
91
+ "domain": domain,
92
+ "port": port,
93
+ "protocol": protocol,
94
+ },
85
95
  )
86
96
  with open(self.html_403, "r", encoding="utf-8") as f:
87
97
  custom_403_page = f.read()
@@ -150,7 +160,15 @@ class HttpsHandler:
150
160
  ssl_client_socket = client_context.wrap_socket(
151
161
  client_socket, server_side=True, do_handshake_on_connect=False
152
162
  )
153
- ssl_client_socket.do_handshake()
163
+ try:
164
+ ssl_client_socket.do_handshake()
165
+ except ssl.SSLError as e:
166
+ if "TLSV1_ALERT_UNKNOWN_CA" in str(e):
167
+ self.console_logger.debug("Client refused cert: %s", e)
168
+ ssl_client_socket.close()
169
+ raise ConnectionAbortedError("Client refused SSL cert")
170
+ else:
171
+ raise
154
172
  return ssl_client_socket
155
173
 
156
174
  def _wrap_server_socket_with_ssl(self, server_socket, server_host):
@@ -171,7 +189,7 @@ class HttpsHandler:
171
189
  )
172
190
  return ssl_server_socket
173
191
 
174
- def _process_first_ssl_request(self, ssl_client_socket, server_host):
192
+ def _process_first_ssl_request(self, ssl_client_socket, server_host, first_line):
175
193
  """
176
194
  Reads and processes the first SSL client request, extracts the method and full URL.
177
195
  """
@@ -188,15 +206,6 @@ class HttpsHandler:
188
206
  if self._is_blocked(f"{server_host}{path}"):
189
207
  return None, full_url, True
190
208
 
191
- if not self.logger_config.no_logging_access:
192
- self.logger_config.access_logger.info(
193
- "%s - %s - %s %s",
194
- ssl_client_socket.getpeername()[0],
195
- f"https://{server_host}",
196
- method,
197
- full_url,
198
- )
199
-
200
209
  return first_request, full_url, False
201
210
  except Exception as e:
202
211
  self.logger_config.error_logger.error(f"SSL request processing error : {e}")
@@ -221,6 +230,12 @@ class HttpsHandler:
221
230
 
222
231
  not_inspect = self._should_skip_inspection(server_host)
223
232
 
233
+ thread_id = threading.get_ident()
234
+ self.active_connections[thread_id] = {
235
+ "bytes_sent": 0,
236
+ "bytes_received": 0,
237
+ }
238
+
224
239
  if self.ssl_config.ssl_inspect and not not_inspect:
225
240
  try:
226
241
  cert_path, key_path = generate_certificate(
@@ -236,6 +251,8 @@ class HttpsHandler:
236
251
  client_socket, cert_path, key_path
237
252
  )
238
253
 
254
+ tls_version = ssl_client_socket.version() or "unknown"
255
+
239
256
  server_socket = self._establish_server_connection(
240
257
  server_host, server_port
241
258
  )
@@ -244,7 +261,7 @@ class HttpsHandler:
244
261
  )
245
262
 
246
263
  first_request, full_url, is_blocked = self._process_first_ssl_request(
247
- ssl_client_socket, server_host
264
+ ssl_client_socket, server_host, first_line
248
265
  )
249
266
  if is_blocked:
250
267
  self._send_403(ssl_client_socket, target, first_line)
@@ -254,9 +271,31 @@ class HttpsHandler:
254
271
  return
255
272
 
256
273
  ssl_server_socket.sendall(first_request.encode())
274
+ client_ip = ssl_client_socket.getpeername()[0]
275
+ bytes_sent, bytes_received = self.transfer_data_between_sockets(
276
+ ssl_client_socket, ssl_server_socket
277
+ )
257
278
 
258
- self.transfer_data_between_sockets(ssl_client_socket, ssl_server_socket)
279
+ if not self.logger_config.no_logging_access:
280
+ method, _, protocol = first_line.split(" ")
281
+ self.logger_config.access_logger.info(
282
+ "",
283
+ extra={
284
+ "ip_src": client_ip,
285
+ "url": target,
286
+ "method": method,
287
+ "domain": server_host,
288
+ "port": server_port,
289
+ "protocol": protocol,
290
+ "bytes_sent": bytes_sent,
291
+ "bytes_received": bytes_received,
292
+ "tls_version": tls_version,
293
+ },
294
+ )
259
295
 
296
+ except ConnectionAbortedError:
297
+ self.active_connections.pop(threading.get_ident(), None)
298
+ return
260
299
  except ssl.SSLError as e:
261
300
  self.console_logger.error("SSL error: %s", str(e))
262
301
  except socket.error as e:
@@ -267,17 +306,31 @@ class HttpsHandler:
267
306
 
268
307
  else:
269
308
  try:
270
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
271
- server_socket.connect((server_host, server_port))
309
+ server_socket = self._establish_server_connection(
310
+ server_host, server_port
311
+ )
272
312
  client_socket.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")
313
+
314
+ client_ip = client_socket.getpeername()[0]
315
+ bytes_sent, bytes_received = self.transfer_data_between_sockets(
316
+ client_socket, server_socket
317
+ )
318
+
273
319
  if not self.logger_config.no_logging_access:
320
+ _, _, protocol = first_line.split(" ")
274
321
  self.logger_config.access_logger.info(
275
- "%s - %s - %s",
276
- client_socket.getpeername()[0],
277
- f"https://{server_host}",
278
- first_line,
322
+ "",
323
+ extra={
324
+ "ip_src": client_ip,
325
+ "url": target,
326
+ "method": "CONNECT",
327
+ "domain": server_host,
328
+ "port": server_port,
329
+ "protocol": protocol,
330
+ "bytes_sent": bytes_sent,
331
+ "bytes_received": bytes_received,
332
+ },
279
333
  )
280
- self.transfer_data_between_sockets(client_socket, server_socket)
281
334
  except (
282
335
  socket.timeout,
283
336
  socket.gaierror,
@@ -295,6 +348,8 @@ class HttpsHandler:
295
348
  )
296
349
  client_socket.sendall(response.encode())
297
350
  client_socket.close()
351
+ finally:
352
+ self.active_connections.pop(thread_id, None)
298
353
 
299
354
  def transfer_data_between_sockets(self, client_socket, server_socket):
300
355
  """
@@ -327,8 +382,12 @@ class HttpsHandler:
327
382
  self.console_logger.debug("Closing connection.")
328
383
  client_socket.close()
329
384
  server_socket.close()
385
+ bytes_sent = self.active_connections[thread_id]["bytes_sent"]
386
+ bytes_received = self.active_connections[thread_id][
387
+ "bytes_received"
388
+ ]
330
389
  self.active_connections.pop(threading.get_ident(), None)
331
- return
390
+ return bytes_sent, bytes_received
332
391
  if sock is client_socket:
333
392
  server_socket.sendall(data)
334
393
  self.active_connections[thread_id]["bytes_sent"] += len(data)
@@ -272,7 +272,7 @@ def start_flask_server(proxy_server, flask_port, flask_pass, debug) -> None:
272
272
  "ssl_config": (
273
273
  proxy_server.ssl_config.to_dict() if proxy_server.ssl_config else None
274
274
  ),
275
- "flask_port": proxy_server.flask_port,
275
+ "flask_port": proxy_server.monitoring_config.flask_port,
276
276
  }
277
277
  return jsonify(config_data)
278
278
 
@@ -56,6 +56,8 @@ def main():
56
56
  )
57
57
 
58
58
  console_format = config.get("Logging", "console_format", fallback=None)
59
+ access_log_format = config.get("Logging", "access_log_format", fallback=None)
60
+ block_log_format = config.get("Logging", "block_log_format", fallback=None)
59
61
  datefmt = config.get("Logging", "datefmt", fallback=None)
60
62
 
61
63
  logger_config = ProxyConfigLogger(
@@ -74,9 +76,44 @@ def main():
74
76
  console_format=(
75
77
  console_format
76
78
  if console_format is not None
77
- else "%(asctime)s - %(levelname)s - %(message)s"
78
- ),
79
- datefmt=datefmt if datefmt is not None else "%d/%m/%Y %H:%M:%S",
79
+ else (
80
+ "date=%(asctime)s "
81
+ "level=%(levelname)s "
82
+ "file=%(filename)s "
83
+ "function=%(funcName)s "
84
+ "message=%(message)s"
85
+ )
86
+ ),
87
+ access_log_format=(
88
+ access_log_format
89
+ if access_log_format is not None
90
+ else (
91
+ "date=%(asctime)s "
92
+ "ip_src=%(ip_src)s "
93
+ "url=%(url)s "
94
+ "method=%(method)s "
95
+ "domain=%(domain)s "
96
+ "port=%(port)s "
97
+ "protocol=%(protocol)s "
98
+ "bytes_sent=%(bytes_sent)s "
99
+ "bytes_received=%(bytes_received)s "
100
+ "tls_version=%(tls_version)s"
101
+ )
102
+ ),
103
+ block_log_format=(
104
+ block_log_format
105
+ if block_log_format is not None
106
+ else (
107
+ "date=%(asctime)s "
108
+ "ip_src=%(ip_src)s "
109
+ "url=%(url)s "
110
+ "method=%(method)s "
111
+ "domain=%(domain)s "
112
+ "port=%(port)s "
113
+ "protocol=%(protocol)s"
114
+ )
115
+ ),
116
+ datefmt=datefmt if datefmt is not None else "%Y-%m-%d %H:%M:%S",
80
117
  )
81
118
 
82
119
  filter_config = ProxyConfigFilter(
@@ -105,11 +105,17 @@ class ProxyServer:
105
105
  self.console_logger = configure_console_logger(self.logger_config)
106
106
  if not self.logger_config.no_logging_access:
107
107
  self.logger_config.access_logger = configure_file_logger(
108
- self.logger_config.access_log, "AccessLogger"
108
+ self.logger_config.access_log,
109
+ "AccessLogger",
110
+ self.logger_config.access_log_format,
111
+ self.logger_config.datefmt,
109
112
  )
110
113
  if not self.logger_config.no_logging_block:
111
114
  self.logger_config.block_logger = configure_file_logger(
112
- self.logger_config.block_log, "BlockLogger"
115
+ self.logger_config.block_log,
116
+ "BlockLogger",
117
+ self.logger_config.block_log_format,
118
+ self.logger_config.datefmt,
113
119
  )
114
120
 
115
121
  # Configuration files
@@ -63,6 +63,8 @@ class ProxyConfigLogger:
63
63
  no_logging_access: bool
64
64
  no_logging_block: bool
65
65
  console_format: str
66
+ access_log_format: str
67
+ block_log_format: str
66
68
  datefmt: str
67
69
 
68
70
  def to_dict(self):
@@ -9,6 +9,23 @@ import os
9
9
  import colorlog
10
10
 
11
11
 
12
+ class SafeFormatter(logging.Formatter):
13
+ def format(self, record):
14
+ if self._fmt:
15
+ for key in self._fmt.split("%("):
16
+ if ")" in key:
17
+ var = key.split(")")[0]
18
+ if not hasattr(record, var):
19
+ setattr(record, var, "")
20
+ else:
21
+ val = getattr(record, var)
22
+ if isinstance(val, str):
23
+ setattr(
24
+ record, var, val.replace("\n", "").replace("\r", "")
25
+ )
26
+ return super().format(record)
27
+
28
+
12
29
  def configure_console_logger(logger_config) -> logging.Logger:
13
30
  """
14
31
  Configures and returns a logger that outputs log messages to the console.
@@ -41,13 +58,18 @@ def configure_console_logger(logger_config) -> logging.Logger:
41
58
  return console_logger
42
59
 
43
60
 
44
- def configure_file_logger(log_path: str, name: str) -> logging.Logger:
61
+ def configure_file_logger(
62
+ log_path: str, name: str, log_format: str, datefmt: str
63
+ ) -> logging.Logger:
45
64
  """
46
65
  Configures and returns a logger that writes log messages to a specified file.
47
66
 
48
67
  Args:
49
68
  log_path (str): The path where the log file will be created or appended to.
50
69
  name (str): Logger's name.
70
+ log_format (str): The format string for log messages
71
+ (e.g., with fields like %(ip_src)s, %(method)s).
72
+ datefmt (str): The format string for timestamps (e.g., "%d/%m/%Y %H:%M:%S").
51
73
 
52
74
  Returns:
53
75
  logging.Logger: A logger instance that writes to the specified log file.
@@ -56,6 +78,6 @@ def configure_file_logger(log_path: str, name: str) -> logging.Logger:
56
78
  file_logger = logging.getLogger(name)
57
79
  file_logger.setLevel(logging.INFO)
58
80
  file_handler = logging.FileHandler(log_path)
59
- file_handler.setFormatter(logging.Formatter("%(asctime)s - %(message)s"))
81
+ file_handler.setFormatter(SafeFormatter(log_format, datefmt=datefmt))
60
82
  file_logger.addHandler(file_handler)
61
83
  return file_logger
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyproxytools
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Lightweight and fast python web proxy
5
- Author: 6C656C65
5
+ Author: pyproxytools
6
6
  License-Expression: MIT
7
- Project-URL: Documentation, https://github.com/6C656C65/pyproxy/wiki
8
- Project-URL: Issue tracker, https://github.com/6C656C65/pyproxy/issues
7
+ Project-URL: Documentation, https://github.com/pyproxytools/pyproxy/wiki
8
+ Project-URL: Issue tracker, https://github.com/pyproxytools/pyproxy/issues
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Natural Language :: English
@@ -39,14 +39,14 @@ Dynamic: license-file
39
39
  **pyproxy** is a lightweight, fast, and customizable Python-based web proxy server designed to handle both HTTP and HTTPS traffic efficiently. It can be used for various purposes, including web scraping, traffic monitoring, and content filtering.
40
40
 
41
41
  <p align="center">
42
- <img src="https://img.shields.io/github/license/6C656C65/pyproxy?style=for-the-badge">
43
- <img src="https://img.shields.io/github/issues/6C656C65/pyproxy?style=for-the-badge">
44
- <img src="https://img.shields.io/github/issues-closed/6C656C65/pyproxy?style=for-the-badge">
42
+ <img src="https://img.shields.io/github/license/pyproxytools/pyproxy?style=for-the-badge">
43
+ <img src="https://img.shields.io/github/issues/pyproxytools/pyproxy?style=for-the-badge">
44
+ <img src="https://img.shields.io/github/issues-closed/pyproxytools/pyproxy?style=for-the-badge">
45
45
  <br>
46
- <img src="https://img.shields.io/github/forks/6C656C65/pyproxy?style=for-the-badge">
47
- <img src="https://img.shields.io/github/stars/6C656C65/pyproxy?style=for-the-badge">
48
- <img src="https://img.shields.io/github/commit-activity/w/6C656C65/pyproxy?style=for-the-badge">
49
- <img src="https://img.shields.io/github/contributors/6C656C65/pyproxy?style=for-the-badge">
46
+ <img src="https://img.shields.io/github/forks/pyproxytools/pyproxy?style=for-the-badge">
47
+ <img src="https://img.shields.io/github/stars/pyproxytools/pyproxy?style=for-the-badge">
48
+ <img src="https://img.shields.io/github/commit-activity/w/pyproxytools/pyproxy?style=for-the-badge">
49
+ <img src="https://img.shields.io/github/contributors/pyproxytools/pyproxy?style=for-the-badge">
50
50
  <br>
51
51
  <img src="https://img.shields.io/pypi/v/pyproxytools?style=for-the-badge">
52
52
  <img src="https://img.shields.io/pypi/pyversions/pyproxytools?style=for-the-badge">
@@ -81,18 +81,24 @@ pip install pyproxytools
81
81
 
82
82
  ### Install from source
83
83
  ```bash
84
- git clone https://github.com/6C656C65/pyproxy.git
84
+ git clone https://github.com/pyproxytools/pyproxy.git
85
85
  cd pyproxy
86
86
  pip install -r requirements.txt
87
87
  ```
88
88
 
89
89
  ### Install with Docker
90
90
  ```bash
91
- docker pull ghcr.io/6c656c65/pyproxy:latest
92
- docker run -d ghcr.io/6c656c65/pyproxy:latest
91
+ docker pull ghcr.io/pyproxytools/pyproxy:latest
92
+ docker run -d ghcr.io/pyproxytools/pyproxy:latest
93
93
  ```
94
94
  You can use slim images by adding `-slim` to the end of the tags
95
95
 
96
+ ### Install with Compose
97
+ ```bash
98
+ wget https://raw.githubusercontent.com/pyproxytools/pyproxy/main/docker-compose.yml
99
+ docker-compose up -d
100
+ ```
101
+
96
102
  ## 🚀 **Usage**
97
103
 
98
104
  ### Start the proxy
@@ -103,7 +109,7 @@ The proxy will be available at: `0.0.0.0:8080`.
103
109
  The access log will be available at `./logs/access.log`.
104
110
 
105
111
  ## 📚 **Documentation**
106
- If you encounter any problems, or if you want to use the program in a particular way, I advise you to read the [documentation](https://github.com/6C656C65/pyproxy/wiki).
112
+ If you encounter any problems, or if you want to use the program in a particular way, I advise you to read the [documentation](https://github.com/pyproxytools/pyproxy/wiki).
107
113
 
108
114
  ## 🔧 **To do**
109
115
 
@@ -124,12 +130,12 @@ Contributions are welcome and appreciated! If you'd like to improve this project
124
130
 
125
131
  ## 📦 Deployment with Ansible
126
132
 
127
- If you want to deploy **pyproxy** automatically to remote servers (via source or Docker), an official [Ansible role](https://github.com/6C656C65/pyproxy_ansible) is available:
133
+ If you want to deploy **pyproxy** automatically to remote servers (via source or Docker), an official [Ansible role](https://github.com/pyproxytools/pyproxy-ansible) is available:
128
134
 
129
135
  * 🔧 Install from source or run as a Docker container
130
136
  * 📁 Supports customization of ports, versions, and paths
131
137
  * 🚀 Easily integrable into your infrastructure or CI/CD pipelines
132
138
 
133
- 👉 Check out the [ansible-role-pyproxy](https://github.com/6C656C65/pyproxy_ansible) repository for more details and usage instructions.
139
+ 👉 Check out the [pyproxy-ansible](https://github.com/pyproxytools/pyproxy-ansible) repository for more details and usage instructions.
134
140
 
135
141
  ---
@@ -14,10 +14,15 @@ from pyproxy.utils.logger import configure_console_logger, configure_file_logger
14
14
 
15
15
  class DummyLoggerConfig:
16
16
  def __init__(self, console_format=None, datefmt=None):
17
- self.console_format = (
18
- console_format or "%(log_color)s%(asctime)s - %(levelname)s - %(message)s"
17
+ self.console_format = console_format or (
18
+ "%(log_color)s"
19
+ "date=%(asctime)s "
20
+ "level=%(levelname)s "
21
+ "file=%(filename)s "
22
+ "function=%(funcName)s "
23
+ "message=%(message)s"
19
24
  )
20
- self.datefmt = datefmt or "%d/%m/%Y %H:%M:%S"
25
+ self.datefmt = datefmt or "%Y-%m-%d %H:%M:%S"
21
26
 
22
27
 
23
28
  class TestLogger(unittest.TestCase):
@@ -56,11 +61,15 @@ class TestLogger(unittest.TestCase):
56
61
  mock_file_handler.return_value = mock_handler_instance
57
62
 
58
63
  log_path = "logs/test.log"
59
- logger = configure_file_logger(log_path, "TestLogger")
64
+ log_name = "TestLogger"
65
+ log_format = "%(asctime)s - %(levelname)s - %(message)s"
66
+ datefmt = "%Y-%m-%d %H:%M:%S"
67
+ logger = configure_file_logger(log_path, log_name, log_format, datefmt)
60
68
 
61
- self.assertTrue(logger.hasHandlers())
62
- self.assertEqual(logger.level, logging.INFO)
69
+ self.assertTrue(logger.hasHandlers(), "Logger should have handlers.")
70
+ self.assertEqual(logger.level, logging.INFO, "Logger level should be INFO.")
63
71
  mock_file_handler.assert_called_once_with(log_path)
72
+ mock_handler_instance.setFormatter.assert_called_once()
64
73
 
65
74
  def tearDown(self):
66
75
  """
File without changes
File without changes