Skip to content

Commit 79cb5b7

Browse files
Add a skeleton app to demonstrate how to use proxy.py for standalone projects (abhinavsingh#1029)
* Add a skeleton app structure * Update `README.md` for skeleton app * Add `skeleton-app` to pre commit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update readme Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 627b42f commit 79cb5b7

File tree

11 files changed

+230
-45
lines changed

11 files changed

+230
-45
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ repos:
169169
- --strict-optional
170170
- benchmark/
171171
- examples/
172+
- skeleton/
172173
- proxy/
173174
- tests/
174175
pass_filenames: false

README.md

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,7 +1784,9 @@ Listed below are a few strategies for using `proxy.py` in your private/productio
17841784

17851785
> You MUST `avoid forking` the repository *"just"* to put your plugin code in `proxy/plugin` directory. Forking is recommended workflow for project contributors, NOT for project users.
17861786
1787-
Instead, use one of the suggested approaches from below. Then load your plugins using `--plugin`, `--plugins` flags or `plugin` kwargs.
1787+
- Instead, use one of the suggested approaches from below.
1788+
- Then load your plugins using `--plugin`, `--plugins` flags or `plugin` kwargs.
1789+
- See [skeleton](https://github.com/abhinavsingh/proxy.py/tree/develop/skeleton) app for example standalone project using `proxy.py`.
17881790

17891791
### Via Requirements
17901792

@@ -2243,33 +2245,33 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
22432245
[--tunnel-ssh-key-passphrase TUNNEL_SSH_KEY_PASSPHRASE]
22442246
[--tunnel-remote-port TUNNEL_REMOTE_PORT] [--enable-events]
22452247
[--threadless] [--threaded] [--num-workers NUM_WORKERS]
2246-
[--backlog BACKLOG] [--hostname HOSTNAME] [--port PORT]
2247-
[--port-file PORT_FILE] [--unix-socket-path UNIX_SOCKET_PATH]
2248-
[--local-executor LOCAL_EXECUTOR] [--num-acceptors NUM_ACCEPTORS]
2249-
[--version] [--log-level LOG_LEVEL] [--log-file LOG_FILE]
2250-
[--log-format LOG_FORMAT] [--open-file-limit OPEN_FILE_LIMIT]
2248+
[--local-executor LOCAL_EXECUTOR] [--backlog BACKLOG]
2249+
[--hostname HOSTNAME] [--port PORT] [--port-file PORT_FILE]
2250+
[--unix-socket-path UNIX_SOCKET_PATH]
2251+
[--num-acceptors NUM_ACCEPTORS] [--version] [--log-level LOG_LEVEL]
2252+
[--log-file LOG_FILE] [--log-format LOG_FORMAT]
2253+
[--open-file-limit OPEN_FILE_LIMIT]
22512254
[--plugins PLUGINS [PLUGINS ...]] [--enable-dashboard]
2252-
[--enable-ssh-tunnel] [--work-klass WORK_KLASS]
2253-
[--pid-file PID_FILE] [--enable-conn-pool] [--key-file KEY_FILE]
2255+
[--basic-auth BASIC_AUTH] [--enable-ssh-tunnel]
2256+
[--work-klass WORK_KLASS] [--pid-file PID_FILE]
2257+
[--enable-proxy-protocol] [--enable-conn-pool] [--key-file KEY_FILE]
22542258
[--cert-file CERT_FILE] [--client-recvbuf-size CLIENT_RECVBUF_SIZE]
22552259
[--server-recvbuf-size SERVER_RECVBUF_SIZE] [--timeout TIMEOUT]
2256-
[--enable-proxy-protocol] [--disable-http-proxy]
2257-
[--disable-headers DISABLE_HEADERS] [--ca-key-file CA_KEY_FILE]
2258-
[--ca-cert-dir CA_CERT_DIR] [--ca-cert-file CA_CERT_FILE]
2259-
[--ca-file CA_FILE] [--ca-signing-key-file CA_SIGNING_KEY_FILE]
2260-
[--auth-plugin AUTH_PLUGIN] [--basic-auth BASIC_AUTH]
2261-
[--cache-dir CACHE_DIR]
2262-
[--filtered-upstream-hosts FILTERED_UPSTREAM_HOSTS]
2263-
[--enable-web-server] [--enable-static-server]
2264-
[--static-server-dir STATIC_SERVER_DIR]
2260+
[--disable-http-proxy] [--disable-headers DISABLE_HEADERS]
2261+
[--ca-key-file CA_KEY_FILE] [--ca-cert-dir CA_CERT_DIR]
2262+
[--ca-cert-file CA_CERT_FILE] [--ca-file CA_FILE]
2263+
[--ca-signing-key-file CA_SIGNING_KEY_FILE]
2264+
[--auth-plugin AUTH_PLUGIN] [--cache-dir CACHE_DIR]
2265+
[--proxy-pool PROXY_POOL] [--enable-web-server]
2266+
[--enable-static-server] [--static-server-dir STATIC_SERVER_DIR]
22652267
[--min-compression-length MIN_COMPRESSION_LENGTH]
22662268
[--pac-file PAC_FILE] [--pac-file-url-path PAC_FILE_URL_PATH]
2267-
[--proxy-pool PROXY_POOL]
2269+
[--cloudflare-dns-mode CLOUDFLARE_DNS_MODE]
2270+
[--filtered-upstream-hosts FILTERED_UPSTREAM_HOSTS]
22682271
[--filtered-client-ips FILTERED_CLIENT_IPS]
22692272
[--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG]
2270-
[--cloudflare-dns-mode CLOUDFLARE_DNS_MODE]
22712273

2272-
proxy.py v2.4.0rc7.dev12+gd234339.d20220116
2274+
proxy.py v2.4.0rc7.dev28+gfbd7b46.d20220120
22732275

22742276
options:
22752277
-h, --help show this help message and exit
@@ -2299,6 +2301,14 @@ options:
22992301
handle each client connection.
23002302
--num-workers NUM_WORKERS
23012303
Defaults to number of CPU cores.
2304+
--local-executor LOCAL_EXECUTOR
2305+
Default: 1. Enabled by default. Use 0 to disable. When
2306+
enabled acceptors will make use of local (same
2307+
process) executor instead of distributing load across
2308+
remote (other process) executors. Enable this option
2309+
to achieve CPU affinity between acceptors and
2310+
executors, instead of using underlying OS kernel
2311+
scheduling algorithm.
23022312
--backlog BACKLOG Default: 100. Maximum number of pending connections to
23032313
proxy server
23042314
--hostname HOSTNAME Default: 127.0.0.1. Server IP address.
@@ -2309,14 +2319,6 @@ options:
23092319
--unix-socket-path UNIX_SOCKET_PATH
23102320
Default: None. Unix socket path to use. When provided
23112321
--host and --port flags are ignored
2312-
--local-executor LOCAL_EXECUTOR
2313-
Default: 1. Enabled by default. Use 0 to disable. When
2314-
enabled acceptors will make use of local (same
2315-
process) executor instead of distributing load across
2316-
remote (other process) executors. Enable this option
2317-
to achieve CPU affinity between acceptors and
2318-
executors, instead of using underlying OS kernel
2319-
scheduling algorithm.
23202322
--num-acceptors NUM_ACCEPTORS
23212323
Defaults to number of CPU cores.
23222324
--version, -v Prints proxy.py version.
@@ -2335,11 +2337,17 @@ options:
23352337
Comma separated plugins. You may use --plugins flag
23362338
multiple times.
23372339
--enable-dashboard Default: False. Enables proxy.py dashboard.
2340+
--basic-auth BASIC_AUTH
2341+
Default: No authentication. Specify colon separated
2342+
user:password to enable basic authentication.
23382343
--enable-ssh-tunnel Default: False. Enable SSH tunnel.
23392344
--work-klass WORK_KLASS
23402345
Default: proxy.http.HttpProtocolHandler. Work klass to
23412346
use for work execution.
23422347
--pid-file PID_FILE Default: None. Save "parent" process ID to a file.
2348+
--enable-proxy-protocol
2349+
Default: False. If used, will enable proxy protocol.
2350+
Only version 1 is currently supported.
23432351
--enable-conn-pool Default: False. (WIP) Enable upstream connection
23442352
pooling.
23452353
--key-file KEY_FILE Default: None. Server key file to enable end-to-end
@@ -2358,9 +2366,6 @@ options:
23582366
--timeout TIMEOUT Default: 10.0. Number of seconds after which an
23592367
inactive connection must be dropped. Inactivity is
23602368
defined by no data sent or received by the client.
2361-
--enable-proxy-protocol
2362-
Default: False. If used, will enable proxy protocol.
2363-
Only version 1 is currently supported.
23642369
--disable-http-proxy Default: False. Whether to disable
23652370
proxy.HttpProxyPlugin.
23662371
--disable-headers DISABLE_HEADERS
@@ -2388,17 +2393,13 @@ options:
23882393
generation of HTTPS certificates. If used, must also
23892394
pass --ca-key-file and --ca-cert-file
23902395
--auth-plugin AUTH_PLUGIN
2391-
Default: proxy.http.proxy.AuthPlugin. Auth plugin to
2392-
use instead of default basic auth plugin.
2393-
--basic-auth BASIC_AUTH
2394-
Default: No authentication. Specify colon separated
2395-
user:password to enable basic authentication.
2396+
Default: proxy.http.proxy.auth.AuthPlugin. Auth plugin
2397+
to use instead of default basic auth plugin.
23962398
--cache-dir CACHE_DIR
23972399
Default: A temporary directory. Flag only applicable
23982400
when cache plugin is used with on-disk storage.
2399-
--filtered-upstream-hosts FILTERED_UPSTREAM_HOSTS
2400-
Default: Blocks Facebook. Comma separated list of IPv4
2401-
and IPv6 addresses.
2401+
--proxy-pool PROXY_POOL
2402+
List of upstream proxies to use in the pool
24022403
--enable-web-server Default: False. Whether to enable
24032404
proxy.HttpWebServerPlugin.
24042405
--enable-static-server
@@ -2419,18 +2420,19 @@ options:
24192420
this option enables proxy.HttpWebServerPlugin.
24202421
--pac-file-url-path PAC_FILE_URL_PATH
24212422
Default: /. Web server path to serve the PAC file.
2422-
--proxy-pool PROXY_POOL
2423-
List of upstream proxies to use in the pool
2423+
--cloudflare-dns-mode CLOUDFLARE_DNS_MODE
2424+
Default: security. Either "security" (for malware
2425+
protection) or "family" (for malware and adult content
2426+
protection)
2427+
--filtered-upstream-hosts FILTERED_UPSTREAM_HOSTS
2428+
Default: Blocks Facebook. Comma separated list of IPv4
2429+
and IPv6 addresses.
24242430
--filtered-client-ips FILTERED_CLIENT_IPS
24252431
Default: 127.0.0.1,::1. Comma separated list of IPv4
24262432
and IPv6 addresses.
24272433
--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG
24282434
Default: No config. Comma separated list of IPv4 and
24292435
IPv6 addresses.
2430-
--cloudflare-dns-mode CLOUDFLARE_DNS_MODE
2431-
Default: security. Either "security" (for malware
2432-
protection) or "family" (for malware and adult content
2433-
protection)
24342436

24352437
Proxy.py not working? Report at:
24362438
https://github.com/abhinavsingh/proxy.py/issues/new

check.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
list(REPO_ROOT.glob('*.py')) +
3838
list((REPO_ROOT / 'proxy').rglob('*.py')) +
3939
list((REPO_ROOT / 'examples').rglob('*.py')) +
40+
list((REPO_ROOT / 'skeleton').rglob('*.py')) +
4041
list((REPO_ROOT / 'benchmark').rglob('*.py')) +
4142
list((REPO_ROOT / 'tests').rglob('*.py'))
4243
)

skeleton/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Skeleton App
2+
3+
This directory contains a sample standalone application structure which uses `proxy.py`
4+
via `requirements.txt` file.
5+
6+
## Setup
7+
8+
```console
9+
$ git clone https://github.com/abhinavsingh/proxy.py.git
10+
$ cd proxy.py/skeleton
11+
$ python3 -m venv .venv
12+
$ source .venv/bin/activate
13+
$ pip install -r requirements.txt
14+
```
15+
16+
## Run It
17+
18+
Start your app and make a web request to `/` and a proxy request via the instance. You will
19+
see log lines like this:
20+
21+
```console
22+
$ python -m app
23+
...[redacted]... - Loaded plugin proxy.http.proxy.HttpProxyPlugin
24+
...[redacted]... - Loaded plugin proxy.http.server.HttpWebServerPlugin
25+
...[redacted]... - Loaded plugin app.plugins.MyWebServerPlugin
26+
...[redacted]... - Loaded plugin app.plugins.MyProxyPlugin
27+
...[redacted]... - Listening on 127.0.0.1:9000
28+
...[redacted]... - Started 16 acceptors in threadless (local) mode
29+
...[redacted]... - HttpProtocolException: HttpRequestRejected b"I'm a tea pot"
30+
...[redacted]... - 127.0.0.1:64601 - GET None:None/get - None None - 0 bytes - 0.64ms
31+
...[redacted]... - 127.0.0.1:64622 - GET / - curl/7.77.0 - 0.95ms
32+
```
33+
34+
Voila!!!
35+
36+
That is your custom app skeleton structure built on top of `proxy.py`. Now copy the `app` directory
37+
outside of `proxy.py` repo and create your own git repo. Customize the `app` for your project needs

skeleton/app/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
proxy.py
4+
~~~~~~~~
5+
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
6+
Network monitoring, controls & Application development, testing, debugging.
7+
8+
:copyright: (c) 2013-present by Abhinav Singh and contributors.
9+
:license: BSD, see LICENSE for more details.
10+
"""
11+
from .app import entry_point
12+
13+
14+
__all__ = [
15+
'entry_point',
16+
]

skeleton/app/__main__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
proxy.py
4+
~~~~~~~~
5+
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
6+
Network monitoring, controls & Application development, testing, debugging.
7+
8+
:copyright: (c) 2013-present by Abhinav Singh and contributors.
9+
:license: BSD, see LICENSE for more details.
10+
"""
11+
from app import entry_point
12+
13+
14+
if __name__ == '__main__':
15+
entry_point()

skeleton/app/app.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
proxy.py
4+
~~~~~~~~
5+
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
6+
Network monitoring, controls & Application development, testing, debugging.
7+
8+
:copyright: (c) 2013-present by Abhinav Singh and contributors.
9+
:license: BSD, see LICENSE for more details.
10+
"""
11+
import proxy
12+
13+
14+
def entry_point() -> None:
15+
with proxy.Proxy(
16+
enable_web_server=True,
17+
port=9000,
18+
# NOTE: Pass plugins via *args if you define custom flags.
19+
# Currently plugins passed via **kwargs are not discovered for
20+
# custom flags by proxy.py
21+
#
22+
# See https://github.com/abhinavsingh/proxy.py/issues/871
23+
plugins=[
24+
'app.plugins.MyWebServerPlugin',
25+
'app.plugins.MyProxyPlugin',
26+
],
27+
) as _:
28+
proxy.sleep_loop()

skeleton/app/plugins/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
proxy.py
4+
~~~~~~~~
5+
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
6+
Network monitoring, controls & Application development, testing, debugging.
7+
8+
:copyright: (c) 2013-present by Abhinav Singh and contributors.
9+
:license: BSD, see LICENSE for more details.
10+
"""
11+
from .my_web_plugin import MyWebServerPlugin
12+
from .my_proxy_plugin import MyProxyPlugin
13+
14+
15+
__all__ = [
16+
'MyWebServerPlugin',
17+
'MyProxyPlugin',
18+
]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
proxy.py
4+
~~~~~~~~
5+
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
6+
Network monitoring, controls & Application development, testing, debugging.
7+
8+
:copyright: (c) 2013-present by Abhinav Singh and contributors.
9+
:license: BSD, see LICENSE for more details.
10+
11+
.. spelling::
12+
13+
ip
14+
"""
15+
from typing import Optional
16+
17+
from proxy.http import httpStatusCodes
18+
from proxy.http.proxy import HttpProxyBasePlugin
19+
from proxy.http.parser import HttpParser
20+
from proxy.http.exception import HttpRequestRejected
21+
22+
23+
class MyProxyPlugin(HttpProxyBasePlugin):
24+
"""Drop traffic by inspecting incoming client IP address."""
25+
26+
def before_upstream_connection(
27+
self, request: HttpParser,
28+
) -> Optional[HttpParser]:
29+
assert not self.flags.unix_socket_path and self.client.addr
30+
if self.client.addr[0] in '127.0.0.1,::1'.split(','):
31+
raise HttpRequestRejected(
32+
status_code=httpStatusCodes.I_AM_A_TEAPOT,
33+
reason=b'I\'m a tea pot',
34+
)
35+
return request

skeleton/app/plugins/my_web_plugin.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
proxy.py
4+
~~~~~~~~
5+
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
6+
Network monitoring, controls & Application development, testing, debugging.
7+
8+
:copyright: (c) 2013-present by Abhinav Singh and contributors.
9+
:license: BSD, see LICENSE for more details.
10+
"""
11+
import logging
12+
from typing import List, Tuple
13+
14+
from proxy.http.parser import HttpParser
15+
from proxy.http.server import HttpWebServerBasePlugin, httpProtocolTypes
16+
from proxy.http.responses import okResponse
17+
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
class MyWebServerPlugin(HttpWebServerBasePlugin):
23+
"""Demonstrates inbuilt web server routing using plugin."""
24+
25+
def routes(self) -> List[Tuple[int, str]]:
26+
return [
27+
(httpProtocolTypes.HTTP, r'/$'),
28+
]
29+
30+
def handle_request(self, request: HttpParser) -> None:
31+
self.client.queue(okResponse(content=b'Hello World'))

0 commit comments

Comments
 (0)