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
12 changes: 8 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ CA_CERT_FILE_PATH := ca-cert.pem
CA_SIGNING_KEY_FILE_PATH := ca-signing-key.pem

.PHONY: all https-certificates ca-certificates autopep8 devtools
.PHONY: lib-clean lib-test lib-package lib-release-test lib-release lib-coverage lib-lint lib-profile
.PHONY: lib-version lib-clean lib-test lib-package lib-coverage lib-lint
.PHONY: lib-release-test lib-release lib-profile
.PHONY: container container-run container-release
.PHONY: dashboard dashboard-clean

all: lib-clean lib-test
all: lib-test

devtools:
pushd dashboard && npm run devtools && popd
Expand Down Expand Up @@ -56,6 +57,9 @@ ca-certificates:
python -m proxy.common.pki remove_passphrase \
--private-key-path $(CA_SIGNING_KEY_FILE_PATH)

lib-version:
python version-check.py

lib-clean:
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
Expand All @@ -72,10 +76,10 @@ lib-lint:
flake8 --ignore=W504 --max-line-length=127 --max-complexity=19 proxy/ tests/ setup.py
mypy --strict --ignore-missing-imports proxy/ tests/ setup.py

lib-test: lib-lint
lib-test: lib-clean lib-version lib-lint
pytest -v tests/

lib-package: lib-clean
lib-package: lib-clean lib-version
python setup.py sdist

lib-release-test: lib-package
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Table of Contents
* [API Usage](#api-usage)
* [CLI Usage](#cli-usage)
* [Frequently Asked Questions](#frequently-asked-questions)
* [Threads vs Threadless](#threads-vs-threadless)
* [SyntaxError: invalid syntax](#syntaxerror-invalid-syntax)
* [Unable to load plugins](#unable-to-load-plugins)
* [Unable to connect with proxy.py from remote host](#unable-to-connect-with-proxypy-from-remote-host)
Expand Down Expand Up @@ -1293,6 +1294,18 @@ FILE
Frequently Asked Questions
==========================

## Threads vs Threadless

Pre v2.x, `proxy.py` used to spawn new threads for handling
client requests.

Starting v2.x, `proxy.py` added support for threadless execution of
client requests using `asyncio`.

In future, threadless execution will be the default mode.
Till then if you are interested in trying it out,
start `proxy.py` with `--threadless` flag.

## SyntaxError: invalid syntax

Make sure you are using `Python 3`. Verify the version before running `proxy.py`:
Expand Down
1 change: 1 addition & 0 deletions proxy/py.typed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Marker file for PEP 561. The proxy package uses inline types.
159 changes: 81 additions & 78 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,89 +10,92 @@
"""
from setuptools import setup, find_packages

VERSION = (2, 0, 0)
VERSION = (2, 1, 0)
__version__ = '.'.join(map(str, VERSION[0:3]))
__description__ = '⚡⚡⚡ Fast, Lightweight, Programmable Proxy Server in a single Python file.'
__description__ = '''⚡⚡⚡Fast, Lightweight, Pluggable, TLS interception capable proxy server
focused on Network monitoring, controls & Application development, testing, debugging.'''
__author__ = 'Abhinav Singh'
__author_email__ = 'mailsforabhinav@gmail.com'
__homepage__ = 'https://github.com/abhinavsingh/proxy.py'
__download_url__ = '%s/archive/master.zip' % __homepage__
__license__ = 'BSD'

setup(
name='proxy.py',
version=__version__,
author=__author__,
author_email=__author_email__,
url=__homepage__,
description=__description__,
long_description=open('README.md', 'r', encoding='utf-8').read().strip(),
long_description_content_type='text/markdown',
download_url=__download_url__,
license=__license__,
python_requires='!=2.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
zip_safe=True,
packages=find_packages(exclude=["tests", "tests.*"]),
install_requires=open('requirements.txt', 'r').read().strip().split(),
entry_points={
'console_scripts': [
'proxy = proxy:entry_point'
]
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Environment :: No Input/Output (Daemon)',
'Environment :: Web Environment',
'Environment :: MacOS X',
'Environment :: Plugins',
'Environment :: Win32 (MS Windows)',
'Framework :: Robot Framework',
'Framework :: Robot Framework :: Library',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: System Administrators',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Operating System :: MacOS',
'Operating System :: MacOS :: MacOS 9',
'Operating System :: MacOS :: MacOS X',
'Operating System :: POSIX',
'Operating System :: POSIX :: Linux',
'Operating System :: Unix',
'Operating System :: Microsoft',
'Operating System :: Microsoft :: Windows',
'Operating System :: Microsoft :: Windows :: Windows 10',
'Operating System :: Android',
'Operating System :: OS Independent',
'Programming Language :: Python :: Implementation',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Topic :: Internet',
'Topic :: Internet :: Proxy Servers',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Browsers',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries',
'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
'Topic :: Scientific/Engineering :: Information Analysis',
'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Monitoring',
'Topic :: System :: Networking',
'Topic :: System :: Networking :: Firewalls',
'Topic :: System :: Networking :: Monitoring',
'Topic :: Utilities',
'Typing :: Typed',
],
keywords=(
'http, proxy, http proxy server, proxy server, http server,'
'http web server, proxy framework, web framework, Python3'
if __name__ == '__main__':
setup(
name='proxy.py',
version=__version__,
author=__author__,
author_email=__author_email__,
url=__homepage__,
description=__description__,
long_description=open('README.md', 'r', encoding='utf-8').read().strip(),
long_description_content_type='text/markdown',
download_url=__download_url__,
license=__license__,
python_requires='!=2.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
zip_safe=False,
packages=find_packages(exclude=['tests', 'tests.*']),
package_data={'proxy': ['py.typed']},
install_requires=open('requirements.txt', 'r').read().strip().split(),
entry_points={
'console_scripts': [
'proxy = proxy:entry_point'
]
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Environment :: No Input/Output (Daemon)',
'Environment :: Web Environment',
'Environment :: MacOS X',
'Environment :: Plugins',
'Environment :: Win32 (MS Windows)',
'Framework :: Robot Framework',
'Framework :: Robot Framework :: Library',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: System Administrators',
'Intended Audience :: Science/Research',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Operating System :: MacOS',
'Operating System :: MacOS :: MacOS 9',
'Operating System :: MacOS :: MacOS X',
'Operating System :: POSIX',
'Operating System :: POSIX :: Linux',
'Operating System :: Unix',
'Operating System :: Microsoft',
'Operating System :: Microsoft :: Windows',
'Operating System :: Microsoft :: Windows :: Windows 10',
'Operating System :: Android',
'Operating System :: OS Independent',
'Programming Language :: Python :: Implementation',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Topic :: Internet',
'Topic :: Internet :: Proxy Servers',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Browsers',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries',
'Topic :: Internet :: WWW/HTTP :: HTTP Servers',
'Topic :: Scientific/Engineering :: Information Analysis',
'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Monitoring',
'Topic :: System :: Networking',
'Topic :: System :: Networking :: Firewalls',
'Topic :: System :: Networking :: Monitoring',
'Topic :: Utilities',
'Typing :: Typed',
],
keywords=(
'http, proxy, http proxy server, proxy server, http server,'
'http web server, proxy framework, web framework, Python3'
)
)
)
29 changes: 13 additions & 16 deletions tests/http/test_http_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,7 @@ def test_get_full_parse(self) -> None:
self.assertEqual(self.parser.url.port, None)
self.assertEqual(self.parser.version, b'HTTP/1.1')
self.assertEqual(self.parser.state, httpParserStates.COMPLETE)
self.assertDictContainsSubset(
{b'host': (b'Host', b'example.com')}, self.parser.headers)
self.assertEqual(self.parser.headers[b'host'], (b'Host', b'example.com'))
self.parser.del_headers([b'host'])
self.parser.add_headers([(b'Host', b'example.com')])
self.assertEqual(
Expand Down Expand Up @@ -189,8 +188,7 @@ def test_get_partial_parse1(self) -> None:
self.parser.parse(CRLF * 2)
self.assertEqual(self.parser.total_size, len(pkt) +
(3 * len(CRLF)) + len(host_hdr))
self.assertDictContainsSubset(
{b'host': (b'Host', b'localhost:8080')}, self.parser.headers)
self.assertEqual(self.parser.headers[b'host'], (b'Host', b'localhost:8080'))
self.assertEqual(self.parser.state, httpParserStates.COMPLETE)

def test_get_partial_parse2(self) -> None:
Expand All @@ -207,17 +205,16 @@ def test_get_partial_parse2(self) -> None:
self.assertEqual(self.parser.state, httpParserStates.LINE_RCVD)

self.parser.parse(b'localhost:8080' + CRLF)
self.assertDictContainsSubset(
{b'host': (b'Host', b'localhost:8080')}, self.parser.headers)
self.assertEqual(self.parser.headers[b'host'], (b'Host', b'localhost:8080'))
self.assertEqual(self.parser.buffer, b'')
self.assertEqual(
self.parser.state,
httpParserStates.RCVING_HEADERS)

self.parser.parse(b'Content-Type: text/plain' + CRLF)
self.assertEqual(self.parser.buffer, b'')
self.assertDictContainsSubset(
{b'content-type': (b'Content-Type', b'text/plain')}, self.parser.headers)
self.assertEqual(
self.parser.headers[b'content-type'], (b'Content-Type', b'text/plain'))
self.assertEqual(
self.parser.state,
httpParserStates.RCVING_HEADERS)
Expand All @@ -239,10 +236,10 @@ def test_post_full_parse(self) -> None:
self.assertEqual(self.parser.url.hostname, b'localhost')
self.assertEqual(self.parser.url.port, None)
self.assertEqual(self.parser.version, b'HTTP/1.1')
self.assertDictContainsSubset(
{b'content-type': (b'Content-Type', b'application/x-www-form-urlencoded')}, self.parser.headers)
self.assertDictContainsSubset(
{b'content-length': (b'Content-Length', b'7')}, self.parser.headers)
self.assertEqual(self.parser.headers[b'content-type'],
(b'Content-Type', b'application/x-www-form-urlencoded'))
self.assertEqual(self.parser.headers[b'content-length'],
(b'Content-Length', b'7'))
self.assertEqual(self.parser.body, b'a=b&c=d')
self.assertEqual(self.parser.buffer, b'')
self.assertEqual(self.parser.state, httpParserStates.COMPLETE)
Expand Down Expand Up @@ -376,8 +373,8 @@ def test_response_parse(self) -> None:
b'<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">\n' +
b'<TITLE>301 Moved</TITLE></HEAD><BODY>\n<H1>301 Moved</H1>\nThe document has moved\n' +
b'<A HREF="http://www.google.com/">here</A>.\r\n</BODY></HTML>\r\n')
self.assertDictContainsSubset(
{b'content-length': (b'Content-Length', b'219')}, self.parser.headers)
self.assertEqual(self.parser.headers[b'content-length'],
(b'Content-Length', b'219'))
self.assertEqual(self.parser.state, httpParserStates.COMPLETE)

def test_response_partial_parse(self) -> None:
Expand All @@ -394,8 +391,8 @@ def test_response_partial_parse(self) -> None:
b'X-XSS-Protection: 1; mode=block\r\n',
b'X-Frame-Options: SAMEORIGIN\r\n'
]))
self.assertDictContainsSubset(
{b'x-frame-options': (b'X-Frame-Options', b'SAMEORIGIN')}, self.parser.headers)
self.assertEqual(self.parser.headers[b'x-frame-options'],
(b'X-Frame-Options', b'SAMEORIGIN'))
self.assertEqual(
self.parser.state,
httpParserStates.RCVING_HEADERS)
Expand Down
22 changes: 22 additions & 0 deletions version-check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
proxy.py
~~~~~~~~
⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable
proxy server for Application debugging, testing and development.

:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
"""
import sys
from proxy.common.version import __version__ as lib_version
from setup import __version__ as pkg_version

# This script ensures our versions never run out of sync.
#
# 1. setup.py doesn't import proxy and hence they both use
# their own respective __version__
# 2. TODO: Version is also hardcoded in homebrew stable package
# installer file, but it only needs to match with other
# versions if git branch is master
if lib_version != pkg_version:
sys.exit(1)