Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --num-acceptors flag + Allow work_klass via Proxy context manager kwargs #714

Merged
merged 28 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3b38f2e
Allow overriding work_klass via Proxy context manager kwargs
abhinavsingh Nov 8, 2021
c4fc97f
Decouple acceptor and executor pools
abhinavsingh Nov 9, 2021
3cddf7b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 9, 2021
50b1e14
Add `--num_acceptors` flag and better load balancing
abhinavsingh Nov 9, 2021
6f0aa7e
Merge branch 'override-work-klass' of github.com:abhinavsingh/proxy.p…
abhinavsingh Nov 9, 2021
96d1b51
Remove unused
abhinavsingh Nov 9, 2021
f7aace9
Lint errors
abhinavsingh Nov 9, 2021
a8aeaf5
Another arg not kwarg
abhinavsingh Nov 9, 2021
24c661c
Move start work staticmethods within ExecutorPool
abhinavsingh Nov 9, 2021
6492cf4
mypy fixes
abhinavsingh Nov 9, 2021
3402eb9
Update README with `--num-acceptors` flag
abhinavsingh Nov 9, 2021
3205ccc
Rename `Proxy.pool` to `Proxy.acceptors`
abhinavsingh Nov 9, 2021
c594731
Add SetupShutdownContextManager abstraction
abhinavsingh Nov 9, 2021
e9f1927
Match --num-acceptors logic with PR description
abhinavsingh Nov 9, 2021
5058fd6
Rename executor utility methods and add docstring
abhinavsingh Nov 9, 2021
d42c9ff
Remove work_klass from constructors and pass it via flags
abhinavsingh Nov 9, 2021
730c44c
Update docstring for pools as they no longer accept a work_klass argu…
abhinavsingh Nov 9, 2021
3c9a24b
Turn work_klass into a flag. main() no longer accepts input_args (on…
abhinavsingh Nov 9, 2021
b3b622a
Expose default work klass in README
abhinavsingh Nov 9, 2021
5085538
Expose `HttpProtocolHandler` and `HttpProtocolHandlerPlugin` within `…
abhinavsingh Nov 9, 2021
68d706e
Start to fix tests
abhinavsingh Nov 9, 2021
123248d
Fix tests
abhinavsingh Nov 9, 2021
3a73901
mypy and flake8
abhinavsingh Nov 9, 2021
5e1e860
Trailing comma
abhinavsingh Nov 9, 2021
bf3b466
Remove unused var
abhinavsingh Nov 9, 2021
edb3b3a
Merge branch 'develop' into override-work-klass
abhinavsingh Nov 9, 2021
6fb669a
Unused arg
abhinavsingh Nov 9, 2021
d5aa4ce
uff
abhinavsingh Nov 9, 2021
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
179 changes: 101 additions & 78 deletions README.md

Large diffs are not rendered by default.

13 changes: 6 additions & 7 deletions examples/https_connect_tunnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@

from typing import Any, Optional

from proxy.common.flag import FlagParser
from proxy import Proxy
from proxy.common.utils import build_http_response
from proxy.http.codes import httpStatusCodes
from proxy.http.parser import httpParserStates
from proxy.http.methods import httpMethods
from proxy.core.acceptor import AcceptorPool
from proxy.core.base import BaseTcpTunnelHandler


Expand Down Expand Up @@ -75,11 +74,11 @@ def handle_data(self, data: memoryview) -> Optional[bool]:

def main() -> None:
# This example requires `threadless=True`
with AcceptorPool(
flags=FlagParser.initialize(
port=12345, num_workers=1, threadless=True,
),
work_klass=HttpsConnectTunnelHandler,
with Proxy(
work_klass=HttpsConnectTunnelHandler,
threadless=True,
num_workers=1,
port=12345,
):
try:
while True:
Expand Down
17 changes: 7 additions & 10 deletions examples/ssl_echo_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
import time
from typing import Optional

from proxy.common.flag import FlagParser
from proxy import Proxy
from proxy.common.utils import wrap_socket
from proxy.core.acceptor import AcceptorPool
from proxy.core.connection import TcpClientConnection

from proxy.core.base import BaseTcpServerHandler
Expand Down Expand Up @@ -45,15 +44,13 @@ def handle_data(self, data: memoryview) -> Optional[bool]:

def main() -> None:
# This example requires `threadless=True`
with AcceptorPool(
flags=FlagParser.initialize(
port=12345,
num_workers=1,
threadless=True,
keyfile='https-key.pem',
certfile='https-signed-cert.pem',
),
with Proxy(
work_klass=EchoSSLServerHandler,
threadless=True,
num_workers=1,
port=12345,
keyfile='https-key.pem',
certfile='https-signed-cert.pem',
):
try:
while True:
Expand Down
11 changes: 5 additions & 6 deletions examples/tcp_echo_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
import time
from typing import Optional

from proxy.common.flag import FlagParser
from proxy.core.acceptor import AcceptorPool
from proxy import Proxy
from proxy.core.base import BaseTcpServerHandler


Expand All @@ -30,11 +29,11 @@ def handle_data(self, data: memoryview) -> Optional[bool]:

def main() -> None:
# This example requires `threadless=True`
with AcceptorPool(
flags=FlagParser.initialize(
port=12345, num_workers=1, threadless=True,
),
with Proxy(
work_klass=EchoServerHandler,
threadless=True,
num_workers=1,
port=12345,
):
try:
while True:
Expand Down
16 changes: 6 additions & 10 deletions examples/web_scraper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

from typing import Dict

from proxy.common.flag import FlagParser
from proxy.core.acceptor import Work, AcceptorPool
from proxy import Proxy
from proxy.core.acceptor import Work
from proxy.common.types import Readables, Writables


Expand Down Expand Up @@ -56,15 +56,11 @@ def handle_events(


if __name__ == '__main__':
with AcceptorPool(
flags=FlagParser.initialize(
port=12345,
num_workers=1,
threadless=True,
keyfile='https-key.pem',
certfile='https-signed-cert.pem',
),
with Proxy(
work_klass=WebScraper,
threadless=True,
num_workers=1,
port=12345,
) as pool:
while True:
time.sleep(1)
2 changes: 2 additions & 0 deletions proxy/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def _env_threadless_compliant() -> bool:
DEFAULT_HTTPS_ACCESS_LOG_FORMAT = '{client_ip}:{client_port} - ' + \
'{request_method} {server_host}:{server_port} - ' + \
'{response_bytes} bytes - {connection_time_ms} ms'
DEFAULT_NUM_ACCEPTORS = 0
DEFAULT_NUM_WORKERS = 0
DEFAULT_OPEN_FILE_LIMIT = 1024
DEFAULT_PAC_FILE = None
Expand All @@ -102,6 +103,7 @@ def _env_threadless_compliant() -> bool:
DEFAULT_HTTP_PORT = 80
DEFAULT_HTTPS_PORT = 443
DEFAULT_MAX_SEND_SIZE = 16 * 1024
DEFAULT_WORK_KLASS = 'proxy.http.HttpProtocolHandler'

DEFAULT_DEVTOOLS_DOC_URL = 'http://proxy'
DEFAULT_DEVTOOLS_FRAME_ID = secrets.token_hex(8)
Expand Down
70 changes: 45 additions & 25 deletions proxy/common/flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from .plugins import Plugins
from .types import IpAddress
from .utils import text_, bytes_, is_py2, set_open_file_limit
from .constants import COMMA, DEFAULT_DATA_DIRECTORY_PATH, DEFAULT_NUM_WORKERS
from .constants import COMMA, DEFAULT_DATA_DIRECTORY_PATH, DEFAULT_NUM_ACCEPTORS, DEFAULT_NUM_WORKERS
from .constants import DEFAULT_DEVTOOLS_WS_PATH, DEFAULT_DISABLE_HEADERS, PY2_DEPRECATION_MESSAGE
from .constants import PLUGIN_DASHBOARD, PLUGIN_DEVTOOLS_PROTOCOL
from .constants import PLUGIN_HTTP_PROXY, PLUGIN_INSPECT_TRAFFIC, PLUGIN_PAC_FILE
Expand Down Expand Up @@ -84,8 +84,8 @@ def parse_args(

@staticmethod
def initialize(
input_args: Optional[List[str]]
= None, **opts: Any,
input_args: Optional[List[str]] = None,
**opts: Any,
) -> argparse.Namespace:
if input_args is None:
input_args = []
Expand All @@ -107,13 +107,35 @@ def initialize(
print(__version__)
sys.exit(0)

# proxy.py currently cannot serve over HTTPS and also perform TLS interception
# at the same time. Check if user is trying to enable both feature
# at the same time.
if (args.cert_file and args.key_file) and \
(args.ca_key_file and args.ca_cert_file and args.ca_signing_key_file):
print(
'You can either enable end-to-end encryption OR TLS interception,'
'not both together.',
)
sys.exit(1)

# Setup logging module
Logger.setup_logger(args.log_file, args.log_level, args.log_format)

# Setup limits
set_open_file_limit(args.open_file_limit)

# Load plugins
# Load work_klass
work_klass = opts.get('work_klass', args.work_klass)
work_klass = Plugins.importer(bytes_(work_klass))[0] \
if isinstance(work_klass, str) \
else work_klass

# Generate auth_code required for basic authentication if enabled
auth_code = None
if args.basic_auth:
auth_code = base64.b64encode(bytes_(args.basic_auth))

# Load default plugins along with user provided --plugins
default_plugins = [
bytes_(p)
for p in FlagParser.get_default_plugins(args)
Expand All @@ -123,31 +145,14 @@ def initialize(
for p in opts.get('plugins', args.plugins.split(text_(COMMA)))
if not (isinstance(p, str) and len(p) == 0)
]

# Load default plugins along with user provided --plugins
plugins = Plugins.load(default_plugins + extra_plugins)

# proxy.py currently cannot serve over HTTPS and also perform TLS interception
# at the same time. Check if user is trying to enable both feature
# at the same time.
if (args.cert_file and args.key_file) and \
(args.ca_key_file and args.ca_cert_file and args.ca_signing_key_file):
print(
'You can either enable end-to-end encryption OR TLS interception,'
'not both together.',
)
sys.exit(1)

# Generate auth_code required for basic authentication if enabled
auth_code = None
if args.basic_auth:
auth_code = base64.b64encode(bytes_(args.basic_auth))

# https://github.com/python/mypy/issues/5865
#
# def option(t: object, key: str, default: Any) -> Any:
# return cast(t, opts.get(key, default))

args.work_klass = work_klass
args.plugins = plugins
args.auth_code = cast(
Optional[bytes],
Expand Down Expand Up @@ -235,18 +240,33 @@ def initialize(
socket.AF_INET6 if args.hostname.version == 6 else socket.AF_INET
)
else:
# FIXME: Not true for tests, as this value will be mock
# FIXME: Not true for tests, as this value will be a mock.
#
# It's a problem only on Windows. Instead of a proper
# test level fix, simply commenting this for now.
# fix in the tests, simply commenting this line of assertion
# for now.
#
# assert args.unix_socket_path is None
args.family = socket.AF_INET6 if args.hostname.version == 6 else socket.AF_INET
args.port = cast(int, opts.get('port', args.port))
args.backlog = cast(int, opts.get('backlog', args.backlog))
num_workers = opts.get('num_workers', args.num_workers)
num_workers = num_workers if num_workers is not None else DEFAULT_NUM_WORKERS
args.num_workers = cast(
int, num_workers if num_workers > 0 else multiprocessing.cpu_count(),
)
num_acceptors = opts.get('num_acceptors', args.num_acceptors)
# See https://github.com/abhinavsingh/proxy.py/pull/714 description
# to understand rationale behind the following logic.
#
# --num-workers flag or option was found. We will use
# the same value for num_acceptors when --num-acceptors flag
# is absent.
if num_workers != DEFAULT_NUM_WORKERS and num_acceptors == DEFAULT_NUM_ACCEPTORS:
args.num_acceptors = args.num_workers
else:
args.num_acceptors = cast(
int, num_acceptors if num_acceptors > 0 else multiprocessing.cpu_count(),
)
args.static_server_dir = cast(
str,
opts.get(
Expand Down
2 changes: 2 additions & 0 deletions proxy/core/acceptor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
from .pool import AcceptorPool
from .work import Work
from .threadless import Threadless
from .executors import ThreadlessPool

__all__ = [
'Acceptor',
'AcceptorPool',
'Work',
'Threadless',
'ThreadlessPool',
]
Loading