Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,12 @@ repos:
rev: v2.1.0
hooks:
- id: codespell
exclude: >-
^.+\.min\.js$
exclude: >
(?x)^(
^.+\.ipynb$|
tests/http/test_responses\.py|
^.+\.min\.js$
)$

- repo: https://github.com/adrienverge/yamllint.git
rev: v1.26.2
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ lib-clean:
rm -rf .hypothesis
# Doc RST files are cached and can cause issues
# See https://github.com/abhinavsingh/proxy.py/issues/642#issuecomment-1003444578
rm docs/pkg/*.rst
rm -f docs/pkg/*.rst

lib-dep:
pip install --upgrade pip && \
Expand Down Expand Up @@ -134,7 +134,7 @@ lib-doc:
python -m tox -e build-docs && \
$(OPEN) .tox/build-docs/docs_out/index.html || true

lib-coverage:
lib-coverage: lib-clean
pytest --cov=proxy --cov=tests --cov-report=html tests/ && \
$(OPEN) htmlcov/index.html || true

Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@
- See `--enable-static-server` and `--static-server-dir` flags

- Optimized for large file uploads and downloads
- See `--client-recvbuf-size` and `--server-recvbuf-size` flag
- See `--client-recvbuf-size`, `--server-recvbuf-size`, `--max-sendbuf-size` flags

- `IPv4` and `IPv6` support
- See `--hostname` flag
Expand Down Expand Up @@ -710,6 +710,8 @@ Start `proxy.py` as:
--plugins proxy.plugin.CacheResponsesPlugin
```

You may also use the `--cache-requests` flag to enable request packet caching for inspection.

Verify using `curl -v -x localhost:8899 http://httpbin.org/get`:

```console
Expand Down Expand Up @@ -2278,13 +2280,14 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
[--work-klass WORK_KLASS] [--pid-file PID_FILE]
[--enable-proxy-protocol] [--enable-conn-pool] [--key-file KEY_FILE]
[--cert-file CERT_FILE] [--client-recvbuf-size CLIENT_RECVBUF_SIZE]
[--server-recvbuf-size SERVER_RECVBUF_SIZE] [--timeout TIMEOUT]
[--server-recvbuf-size SERVER_RECVBUF_SIZE]
[--max-sendbuf-size MAX_SENDBUF_SIZE] [--timeout TIMEOUT]
[--disable-http-proxy] [--disable-headers DISABLE_HEADERS]
[--ca-key-file CA_KEY_FILE] [--ca-cert-dir CA_CERT_DIR]
[--ca-cert-file CA_CERT_FILE] [--ca-file CA_FILE]
[--ca-signing-key-file CA_SIGNING_KEY_FILE]
[--auth-plugin AUTH_PLUGIN] [--cache-dir CACHE_DIR]
[--proxy-pool PROXY_POOL] [--enable-web-server]
[--cache-requests] [--proxy-pool PROXY_POOL] [--enable-web-server]
[--enable-static-server] [--static-server-dir STATIC_SERVER_DIR]
[--min-compression-length MIN_COMPRESSION_LENGTH]
[--enable-reverse-proxy] [--pac-file PAC_FILE]
Expand All @@ -2294,7 +2297,7 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
[--filtered-client-ips FILTERED_CLIENT_IPS]
[--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG]

proxy.py v2.4.0rc8.dev17+g59a4335.d20220123
proxy.py v2.4.0rc9.dev8+gea0253d.d20220126

options:
-h, --help show this help message and exit
Expand Down Expand Up @@ -2389,6 +2392,9 @@ options:
--server-recvbuf-size SERVER_RECVBUF_SIZE
Default: 128 KB. Maximum amount of data received from
the server in a single recv() operation.
--max-sendbuf-size MAX_SENDBUF_SIZE
Default: 64 KB. Maximum amount of data to dispatch in
a single send() operation.
--timeout TIMEOUT Default: 10.0. Number of seconds after which an
inactive connection must be dropped. Inactivity is
defined by no data sent or received by the client.
Expand Down Expand Up @@ -2425,6 +2431,7 @@ options:
Default: /Users/abhinavsingh/.proxy/cache. Flag only
applicable when cache plugin is used with on-disk
storage.
--cache-requests Default: False. Whether to also cache request packets.
--proxy-pool PROXY_POOL
List of upstream proxies to use in the pool
--enable-web-server Default: False. Whether to enable
Expand Down
126 changes: 37 additions & 89 deletions examples/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,116 +8,64 @@
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
"""
import time
import sys
import argparse
import threading
import multiprocessing
from typing import Any

from proxy.core.work import (
Work, ThreadlessPool, BaseLocalExecutor, BaseRemoteExecutor,
)
from proxy.core.work import ThreadlessPool
from proxy.common.flag import FlagParser
from proxy.common.backports import NonBlockingQueue


class Task:
"""This will be our work object."""

def __init__(self, payload: bytes) -> None:
self.payload = payload
print(payload)


class TaskWork(Work[Task]):
"""This will be our handler class, created for each received work."""

@staticmethod
def create(*args: Any) -> Task:
"""Work core doesn't know how to create work objects for us, so
we must provide an implementation of create method here."""
return Task(*args)


class LocalTaskExecutor(BaseLocalExecutor):
"""We'll define a local executor which is capable of receiving
log lines over a non blocking queue."""

def work(self, *args: Any) -> None:
task_id = int(time.time())
uid = '%s-%s' % (self.iid, task_id)
self.works[task_id] = self.create(uid, *args)


class RemoteTaskExecutor(BaseRemoteExecutor):

def work(self, *args: Any) -> None:
task_id = int(time.time())
uid = '%s-%s' % (self.iid, task_id)
self.works[task_id] = self.create(uid, *args)


def start_local(flags: argparse.Namespace) -> None:
work_queue = NonBlockingQueue()
executor = LocalTaskExecutor(iid=1, work_queue=work_queue, flags=flags)
from proxy.core.work.task import (
RemoteTaskExecutor, ThreadedTaskExecutor, SingleProcessTaskExecutor,
)

t = threading.Thread(target=executor.run)
t.daemon = True
t.start()

try:
def start_local_thread(flags: argparse.Namespace) -> None:
with ThreadedTaskExecutor(flags=flags) as thread:
i = 0
while True:
work_queue.put(('%d' % i).encode('utf-8'))
thread.executor.work_queue.put(('%d' % i).encode('utf-8'))
i += 1
except KeyboardInterrupt:
pass
finally:
executor.running.set()
t.join()


def start_remote(flags: argparse.Namespace) -> None:
pipe = multiprocessing.Pipe()
work_queue = pipe[0]
executor = RemoteTaskExecutor(iid=1, work_queue=pipe[1], flags=flags)
def start_remote_process(flags: argparse.Namespace) -> None:
with SingleProcessTaskExecutor(flags=flags) as process:
i = 0
while True:
process.work_queue.send(('%d' % i).encode('utf-8'))
i += 1

p = multiprocessing.Process(target=executor.run)
p.daemon = True
p.start()

try:
def start_remote_pool(flags: argparse.Namespace) -> None:
with ThreadlessPool(flags=flags, executor_klass=RemoteTaskExecutor) as pool:
i = 0
while True:
work_queue = pool.work_queues[i % flags.num_workers]
work_queue.send(('%d' % i).encode('utf-8'))
i += 1
except KeyboardInterrupt:
pass
finally:
executor.running.set()
p.join()


def start_remote_pool(flags: argparse.Namespace) -> None:
with ThreadlessPool(flags=flags, executor_klass=RemoteTaskExecutor) as pool:
try:
i = 0
while True:
work_queue = pool.work_queues[i % flags.num_workers]
work_queue.send(('%d' % i).encode('utf-8'))
i += 1
except KeyboardInterrupt:
pass
def main() -> None:
try:
flags = FlagParser.initialize(
sys.argv[2:] + ['--disable-http-proxy'],
work_klass='proxy.core.work.task.TaskHandler',
)
globals()['start_%s' % sys.argv[1]](flags)
except KeyboardInterrupt:
pass


# TODO: TaskWork, LocalTaskExecutor, RemoteTaskExecutor
# should not be needed, abstract those pieces out in the core
# for stateless tasks.
if __name__ == '__main__':
flags = FlagParser.initialize(
['--disable-http-proxy'],
work_klass=TaskWork,
)
start_remote_pool(flags)
# start_remote(flags)
# start_local(flags)
if len(sys.argv) < 2:
print(
'\n'.join([
'Usage:',
' %s <execution-mode>' % sys.argv[0],
' execution-mode can be one of the following:',
' "remote_pool", "remote_process", "local_thread"',
]),
)
sys.exit(1)
main()
5 changes: 3 additions & 2 deletions proxy/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def _env_threadless_compliant() -> bool:
# Defaults
DEFAULT_BACKLOG = 100
DEFAULT_BASIC_AUTH = None
DEFAULT_MAX_SEND_SIZE = 64 * 1024
DEFAULT_BUFFER_SIZE = 128 * 1024
DEFAULT_CA_CERT_DIR = None
DEFAULT_CA_CERT_FILE = None
Expand Down Expand Up @@ -124,14 +125,13 @@ def _env_threadless_compliant() -> bool:
DEFAULT_PORT = 8899
DEFAULT_SERVER_RECVBUF_SIZE = DEFAULT_BUFFER_SIZE
DEFAULT_STATIC_SERVER_DIR = os.path.join(PROXY_PY_DIR, "public")
DEFAULT_MIN_COMPRESSION_LIMIT = 20 # In bytes
DEFAULT_MIN_COMPRESSION_LENGTH = 20 # In bytes
DEFAULT_THREADLESS = _env_threadless_compliant()
DEFAULT_LOCAL_EXECUTOR = True
DEFAULT_TIMEOUT = 10.0
DEFAULT_VERSION = False
DEFAULT_HTTP_PORT = 80
DEFAULT_HTTPS_PORT = 443
DEFAULT_MAX_SEND_SIZE = 16 * 1024
DEFAULT_WORK_KLASS = 'proxy.http.HttpProtocolHandler'
DEFAULT_ENABLE_PROXY_PROTOCOL = False
# 25 milliseconds to keep the loops hot
Expand All @@ -148,6 +148,7 @@ def _env_threadless_compliant() -> bool:
DEFAULT_CACHE_DIRECTORY_PATH = os.path.join(
DEFAULT_DATA_DIRECTORY_PATH, 'cache',
)
DEFAULT_CACHE_REQUESTS = False

# Cor plugins enabled by default or via flags
DEFAULT_ABC_PLUGINS = [
Expand Down
10 changes: 5 additions & 5 deletions proxy/common/flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
PLUGIN_REVERSE_PROXY, DEFAULT_NUM_ACCEPTORS, PLUGIN_INSPECT_TRAFFIC,
DEFAULT_DISABLE_HEADERS, PY2_DEPRECATION_MESSAGE, DEFAULT_DEVTOOLS_WS_PATH,
PLUGIN_DEVTOOLS_PROTOCOL, PLUGIN_WEBSOCKET_TRANSPORT,
DEFAULT_DATA_DIRECTORY_PATH, DEFAULT_MIN_COMPRESSION_LIMIT,
DEFAULT_DATA_DIRECTORY_PATH, DEFAULT_MIN_COMPRESSION_LENGTH,
)


Expand Down Expand Up @@ -335,13 +335,13 @@ def initialize(
args.static_server_dir,
),
)
args.min_compression_limit = cast(
args.min_compression_length = cast(
bool,
opts.get(
'min_compression_limit',
'min_compression_length',
getattr(
args, 'min_compression_limit',
DEFAULT_MIN_COMPRESSION_LIMIT,
args, 'min_compression_length',
DEFAULT_MIN_COMPRESSION_LENGTH,
),
),
)
Expand Down
33 changes: 22 additions & 11 deletions proxy/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from .types import HostPort
from .constants import (
CRLF, COLON, HTTP_1_1, IS_WINDOWS, WHITESPACE, DEFAULT_TIMEOUT,
DEFAULT_THREADLESS,
DEFAULT_THREADLESS, PROXY_AGENT_HEADER_VALUE,
)


Expand Down Expand Up @@ -84,14 +84,30 @@ def bytes_(s: Any, encoding: str = 'utf-8', errors: str = 'strict') -> Any:
def build_http_request(
method: bytes, url: bytes,
protocol_version: bytes = HTTP_1_1,
content_type: Optional[bytes] = None,
headers: Optional[Dict[bytes, bytes]] = None,
body: Optional[bytes] = None,
conn_close: bool = False,
no_ua: bool = False,
) -> bytes:
"""Build and returns a HTTP request packet."""
headers = headers or {}
if content_type is not None:
headers[b'Content-Type'] = content_type
has_transfer_encoding = False
has_user_agent = False
for k, _ in headers.items():
if k.lower() == b'transfer-encoding':
has_transfer_encoding = True
elif k.lower() == b'user-agent':
has_user_agent = True
if body and not has_transfer_encoding:
headers[b'Content-Length'] = bytes_(len(body))
if not has_user_agent and not no_ua:
headers[b'User-Agent'] = PROXY_AGENT_HEADER_VALUE
return build_http_pkt(
[method, url, protocol_version],
headers or {},
headers,
body,
conn_close,
)
Expand All @@ -109,19 +125,14 @@ def build_http_response(
line = [protocol_version, bytes_(status_code)]
if reason:
line.append(reason)
if headers is None:
headers = {}
has_content_length = False
headers = headers or {}
has_transfer_encoding = False
for k, _ in headers.items():
if k.lower() == b'content-length':
has_content_length = True
if k.lower() == b'transfer-encoding':
has_transfer_encoding = True
if body is not None and \
not has_transfer_encoding and \
not has_content_length:
headers[b'Content-Length'] = bytes_(len(body))
break
if not has_transfer_encoding:
headers[b'Content-Length'] = bytes_(len(body)) if body else b'0'
return build_http_pkt(line, headers, body, conn_close)


Expand Down
4 changes: 3 additions & 1 deletion proxy/core/acceptor/acceptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ def run(self) -> None:
for fileno in self.socks:
self.socks[fileno].close()
self.socks.clear()
self.selector.close()
logger.debug('Acceptor#%d shutdown', self.idd)

def _recv_and_setup_socks(self) -> None:
Expand Down Expand Up @@ -207,7 +208,8 @@ def _start_local(self) -> None:
self._lthread.start()

def _stop_local(self) -> None:
if self._lthread is not None and self._local_work_queue is not None:
if self._lthread is not None and \
self._local_work_queue is not None:
self._local_work_queue.put(False)
self._lthread.join()

Expand Down
Loading