mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 18:18:25 +00:00
Test WSGI app calling.
- Factor out test servers into a separate file - Adjust docs to note new Flask dependency
This commit is contained in:
parent
782bbee8c0
commit
1ccb2c5dea
28
README.mkd
28
README.mkd
@ -50,28 +50,24 @@ Requirements
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
* [Python](http://www.python.org) 2.7.x.
|
* [Python](http://www.python.org) 2.7.x.
|
||||||
|
* [netlib](http://pypi.python.org/pypi/netlib) 0.2.2 or newer.
|
||||||
* [PyOpenSSL](http://pypi.python.org/pypi/pyOpenSSL) 0.13 or newer.
|
* [PyOpenSSL](http://pypi.python.org/pypi/pyOpenSSL) 0.13 or newer.
|
||||||
* [pyasn1](http://pypi.python.org/pypi/pyasn1) 0.1.2 or newer.
|
* [pyasn1](http://pypi.python.org/pypi/pyasn1) 0.1.2 or newer.
|
||||||
* [urwid](http://excess.org/urwid/) version 1.1 or newer.
|
* [urwid](http://excess.org/urwid/) version 1.1 or newer.
|
||||||
* [PIL](http://www.pythonware.com/products/pil/) version 1.1 or newer.
|
* [PIL](http://www.pythonware.com/products/pil/) version 1.1 or newer.
|
||||||
* [lxml](http://lxml.de/) version 2.3 or newer.
|
* [lxml](http://lxml.de/) version 2.3 or newer.
|
||||||
* [netlib](http://pypi.python.org/pypi/netlib) 0.2.2 or newer.
|
|
||||||
|
|
||||||
The following auxiliary components may be needed if you plan to hack on
|
|
||||||
mitmproxy:
|
|
||||||
|
|
||||||
* The test suite uses the [nose](http://readthedocs.org/docs/nose/en/latest/) unit testing
|
|
||||||
framework and requires [human_curl](https://github.com/Lispython/human_curl) and
|
|
||||||
[pathod](http://pathod.org).
|
|
||||||
* Rendering the documentation requires [countershape](http://github.com/cortesi/countershape).
|
|
||||||
|
|
||||||
__mitmproxy__ is tested and developed on OSX, Linux and OpenBSD. Windows is not
|
__mitmproxy__ is tested and developed on OSX, Linux and OpenBSD. Windows is not
|
||||||
supported at the moment.
|
officially supported at the moment.
|
||||||
|
|
||||||
You should also make sure that your console environment is set up with the
|
|
||||||
following:
|
|
||||||
|
|
||||||
* EDITOR environment variable to determine the external editor.
|
Hacking
|
||||||
* PAGER environment variable to determine the external pager.
|
-------
|
||||||
* Appropriate entries in your mailcap files to determine external
|
|
||||||
viewers for request and response contents.
|
The following components are needed if you plan to hack on mitmproxy:
|
||||||
|
|
||||||
|
* The test suite uses the [nose](http://readthedocs.org/docs/nose/en/latest/) unit testing
|
||||||
|
framework and requires [human_curl](https://github.com/Lispython/human_curl),
|
||||||
|
[pathod](http://pathod.org) and [flask](http://flask.pocoo.org/).
|
||||||
|
* Rendering the documentation requires [countershape](http://github.com/cortesi/countershape).
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ def _dummysc(config, host, port):
|
|||||||
|
|
||||||
def _errsc(config, host, port):
|
def _errsc(config, host, port):
|
||||||
m = mock.MagicMock(config=config, host=host, port=port)
|
m = mock.MagicMock(config=config, host=host, port=port)
|
||||||
m.connect = mock.MagicMock(side_effect=tcp.NetLibError())
|
m.connect = mock.MagicMock(side_effect=tcp.NetLibError())
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import socket, time
|
import socket, time
|
||||||
from netlib import tcp
|
from netlib import tcp
|
||||||
from libpathod import pathoc
|
from libpathod import pathoc
|
||||||
import tutils
|
import tutils, tservers
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Note that the choice of response code in these tests matters more than you
|
Note that the choice of response code in these tests matters more than you
|
||||||
@ -39,7 +39,19 @@ class SanityMixin:
|
|||||||
assert l.error
|
assert l.error
|
||||||
|
|
||||||
|
|
||||||
class TestHTTP(tutils.HTTPProxTest, SanityMixin):
|
class TestHTTP(tservers.HTTPProxTest, SanityMixin):
|
||||||
|
def test_app(self):
|
||||||
|
p = self.pathoc()
|
||||||
|
ret = p.request("get:'http://testapp/'")
|
||||||
|
assert ret[1] == 200
|
||||||
|
assert ret[4] == "testapp"
|
||||||
|
|
||||||
|
def test_app_err(self):
|
||||||
|
p = self.pathoc()
|
||||||
|
ret = p.request("get:'http://errapp/'")
|
||||||
|
assert ret[1] == 500
|
||||||
|
assert "ValueError" in ret[4]
|
||||||
|
|
||||||
def test_invalid_http(self):
|
def test_invalid_http(self):
|
||||||
t = tcp.TCPClient("127.0.0.1", self.proxy.port)
|
t = tcp.TCPClient("127.0.0.1", self.proxy.port)
|
||||||
t.connect()
|
t.connect()
|
||||||
@ -69,7 +81,7 @@ class TestHTTP(tutils.HTTPProxTest, SanityMixin):
|
|||||||
assert l.response.code == 304
|
assert l.response.code == 304
|
||||||
|
|
||||||
|
|
||||||
class TestHTTPS(tutils.HTTPProxTest, SanityMixin):
|
class TestHTTPS(tservers.HTTPProxTest, SanityMixin):
|
||||||
ssl = True
|
ssl = True
|
||||||
clientcerts = True
|
clientcerts = True
|
||||||
def test_clientcert(self):
|
def test_clientcert(self):
|
||||||
@ -77,15 +89,15 @@ class TestHTTPS(tutils.HTTPProxTest, SanityMixin):
|
|||||||
assert self.last_log()["request"]["clientcert"]["keyinfo"]
|
assert self.last_log()["request"]["clientcert"]["keyinfo"]
|
||||||
|
|
||||||
|
|
||||||
class TestReverse(tutils.ReverseProxTest, SanityMixin):
|
class TestReverse(tservers.ReverseProxTest, SanityMixin):
|
||||||
reverse = True
|
reverse = True
|
||||||
|
|
||||||
|
|
||||||
class TestTransparent(tutils.TransparentProxTest, SanityMixin):
|
class TestTransparent(tservers.TransparentProxTest, SanityMixin):
|
||||||
transparent = True
|
transparent = True
|
||||||
|
|
||||||
|
|
||||||
class TestProxy(tutils.HTTPProxTest):
|
class TestProxy(tservers.HTTPProxTest):
|
||||||
def test_http(self):
|
def test_http(self):
|
||||||
f = self.pathod("304")
|
f = self.pathod("304")
|
||||||
assert f.status_code == 304
|
assert f.status_code == 304
|
||||||
|
183
test/tservers.py
Normal file
183
test/tservers.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import threading, Queue
|
||||||
|
import flask
|
||||||
|
import human_curl as hurl
|
||||||
|
import libpathod.test, libpathod.pathoc
|
||||||
|
from libmproxy import proxy, flow, controller
|
||||||
|
import tutils
|
||||||
|
|
||||||
|
testapp = flask.Flask(__name__)
|
||||||
|
|
||||||
|
@testapp.route("/")
|
||||||
|
def hello():
|
||||||
|
return "testapp"
|
||||||
|
|
||||||
|
@testapp.route("/error")
|
||||||
|
def error():
|
||||||
|
raise ValueError("An exception...")
|
||||||
|
|
||||||
|
|
||||||
|
def errapp(environ, start_response):
|
||||||
|
raise ValueError("errapp")
|
||||||
|
|
||||||
|
|
||||||
|
class TestMaster(flow.FlowMaster):
|
||||||
|
def __init__(self, testq, config):
|
||||||
|
s = proxy.ProxyServer(config, 0)
|
||||||
|
s.apps.add(testapp, "testapp", 80)
|
||||||
|
s.apps.add(errapp, "errapp", 80)
|
||||||
|
state = flow.State()
|
||||||
|
flow.FlowMaster.__init__(self, s, state)
|
||||||
|
self.testq = testq
|
||||||
|
|
||||||
|
def handle(self, m):
|
||||||
|
flow.FlowMaster.handle(self, m)
|
||||||
|
m._ack()
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyThread(threading.Thread):
|
||||||
|
def __init__(self, testq, config):
|
||||||
|
self.tmaster = TestMaster(testq, config)
|
||||||
|
controller.should_exit = False
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port(self):
|
||||||
|
return self.tmaster.server.port
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.tmaster.run()
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
self.tmaster.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
class ProxTestBase:
|
||||||
|
@classmethod
|
||||||
|
def setupAll(cls):
|
||||||
|
cls.tqueue = Queue.Queue()
|
||||||
|
cls.server = libpathod.test.Daemon(ssl=cls.ssl)
|
||||||
|
pconf = cls.get_proxy_config()
|
||||||
|
config = proxy.ProxyConfig(
|
||||||
|
certfile=tutils.test_data.path("data/testkey.pem"),
|
||||||
|
**pconf
|
||||||
|
)
|
||||||
|
cls.proxy = ProxyThread(cls.tqueue, config)
|
||||||
|
cls.proxy.start()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def master(cls):
|
||||||
|
return cls.proxy.tmaster
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def teardownAll(cls):
|
||||||
|
cls.proxy.shutdown()
|
||||||
|
cls.server.shutdown()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.master.state.clear()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scheme(self):
|
||||||
|
return "https" if self.ssl else "http"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def proxies(self):
|
||||||
|
"""
|
||||||
|
The URL base for the server instance.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
(self.scheme, ("127.0.0.1", self.proxy.port))
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def urlbase(self):
|
||||||
|
"""
|
||||||
|
The URL base for the server instance.
|
||||||
|
"""
|
||||||
|
return self.server.urlbase
|
||||||
|
|
||||||
|
def last_log(self):
|
||||||
|
return self.server.last_log()
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPProxTest(ProxTestBase):
|
||||||
|
ssl = None
|
||||||
|
clientcerts = False
|
||||||
|
@classmethod
|
||||||
|
def get_proxy_config(cls):
|
||||||
|
d = dict()
|
||||||
|
if cls.clientcerts:
|
||||||
|
d["clientcerts"] = tutils.test_data.path("data/clientcert")
|
||||||
|
return d
|
||||||
|
|
||||||
|
def pathoc(self, connect_to = None):
|
||||||
|
p = libpathod.pathoc.Pathoc("localhost", self.proxy.port)
|
||||||
|
p.connect(connect_to)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def pathod(self, spec):
|
||||||
|
"""
|
||||||
|
Constructs a pathod request, with the appropriate base and proxy.
|
||||||
|
"""
|
||||||
|
return hurl.get(
|
||||||
|
self.urlbase + "/p/" + spec,
|
||||||
|
proxy=self.proxies,
|
||||||
|
validate_cert=False,
|
||||||
|
#debug=hurl.utils.stdout_debug
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TResolver:
|
||||||
|
def __init__(self, port):
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
def original_addr(self, sock):
|
||||||
|
return ("127.0.0.1", self.port)
|
||||||
|
|
||||||
|
|
||||||
|
class TransparentProxTest(ProxTestBase):
|
||||||
|
ssl = None
|
||||||
|
@classmethod
|
||||||
|
def get_proxy_config(cls):
|
||||||
|
return dict(
|
||||||
|
transparent_proxy = dict(
|
||||||
|
resolver = TResolver(cls.server.port),
|
||||||
|
sslports = []
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def pathod(self, spec):
|
||||||
|
"""
|
||||||
|
Constructs a pathod request, with the appropriate base and proxy.
|
||||||
|
"""
|
||||||
|
r = hurl.get(
|
||||||
|
"http://127.0.0.1:%s"%self.proxy.port + "/p/" + spec,
|
||||||
|
validate_cert=False,
|
||||||
|
#debug=hurl.utils.stdout_debug
|
||||||
|
)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
class ReverseProxTest(ProxTestBase):
|
||||||
|
ssl = None
|
||||||
|
@classmethod
|
||||||
|
def get_proxy_config(cls):
|
||||||
|
return dict(
|
||||||
|
reverse_proxy = (
|
||||||
|
"https" if cls.ssl else "http",
|
||||||
|
"127.0.0.1",
|
||||||
|
cls.server.port
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def pathod(self, spec):
|
||||||
|
"""
|
||||||
|
Constructs a pathod request, with the appropriate base and proxy.
|
||||||
|
"""
|
||||||
|
r = hurl.get(
|
||||||
|
"http://127.0.0.1:%s"%self.proxy.port + "/p/" + spec,
|
||||||
|
validate_cert=False,
|
||||||
|
#debug=hurl.utils.stdout_debug
|
||||||
|
)
|
||||||
|
return r
|
||||||
|
|
167
test/tutils.py
167
test/tutils.py
@ -1,10 +1,8 @@
|
|||||||
import threading, Queue
|
|
||||||
import os, shutil, tempfile
|
import os, shutil, tempfile
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from libmproxy import proxy, flow, controller, utils
|
from libmproxy import flow, utils
|
||||||
from netlib import certutils
|
from netlib import certutils
|
||||||
import human_curl as hurl
|
|
||||||
import libpathod.test, libpathod.pathoc
|
|
||||||
|
|
||||||
def treq(conn=None):
|
def treq(conn=None):
|
||||||
if not conn:
|
if not conn:
|
||||||
@ -42,166 +40,6 @@ def tflow_err():
|
|||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
class TestMaster(flow.FlowMaster):
|
|
||||||
def __init__(self, testq, config):
|
|
||||||
s = proxy.ProxyServer(config, 0)
|
|
||||||
state = flow.State()
|
|
||||||
flow.FlowMaster.__init__(self, s, state)
|
|
||||||
self.testq = testq
|
|
||||||
|
|
||||||
def handle(self, m):
|
|
||||||
flow.FlowMaster.handle(self, m)
|
|
||||||
m._ack()
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyThread(threading.Thread):
|
|
||||||
def __init__(self, testq, config):
|
|
||||||
self.tmaster = TestMaster(testq, config)
|
|
||||||
controller.should_exit = False
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def port(self):
|
|
||||||
return self.tmaster.server.port
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.tmaster.run()
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
self.tmaster.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
class ProxTestBase:
|
|
||||||
@classmethod
|
|
||||||
def setupAll(cls):
|
|
||||||
cls.tqueue = Queue.Queue()
|
|
||||||
cls.server = libpathod.test.Daemon(ssl=cls.ssl)
|
|
||||||
pconf = cls.get_proxy_config()
|
|
||||||
config = proxy.ProxyConfig(
|
|
||||||
certfile=test_data.path("data/testkey.pem"),
|
|
||||||
**pconf
|
|
||||||
)
|
|
||||||
cls.proxy = ProxyThread(cls.tqueue, config)
|
|
||||||
cls.proxy.start()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def master(cls):
|
|
||||||
return cls.proxy.tmaster
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def teardownAll(cls):
|
|
||||||
cls.proxy.shutdown()
|
|
||||||
cls.server.shutdown()
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.master.state.clear()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def scheme(self):
|
|
||||||
return "https" if self.ssl else "http"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def proxies(self):
|
|
||||||
"""
|
|
||||||
The URL base for the server instance.
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
(self.scheme, ("127.0.0.1", self.proxy.port))
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def urlbase(self):
|
|
||||||
"""
|
|
||||||
The URL base for the server instance.
|
|
||||||
"""
|
|
||||||
return self.server.urlbase
|
|
||||||
|
|
||||||
def last_log(self):
|
|
||||||
return self.server.last_log()
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPProxTest(ProxTestBase):
|
|
||||||
ssl = None
|
|
||||||
clientcerts = False
|
|
||||||
@classmethod
|
|
||||||
def get_proxy_config(cls):
|
|
||||||
d = dict()
|
|
||||||
if cls.clientcerts:
|
|
||||||
d["clientcerts"] = test_data.path("data/clientcert")
|
|
||||||
return d
|
|
||||||
|
|
||||||
def pathoc(self, connect_to = None):
|
|
||||||
p = libpathod.pathoc.Pathoc("localhost", self.proxy.port)
|
|
||||||
p.connect(connect_to)
|
|
||||||
return p
|
|
||||||
|
|
||||||
def pathod(self, spec):
|
|
||||||
"""
|
|
||||||
Constructs a pathod request, with the appropriate base and proxy.
|
|
||||||
"""
|
|
||||||
return hurl.get(
|
|
||||||
self.urlbase + "/p/" + spec,
|
|
||||||
proxy=self.proxies,
|
|
||||||
validate_cert=False,
|
|
||||||
#debug=hurl.utils.stdout_debug
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TResolver:
|
|
||||||
def __init__(self, port):
|
|
||||||
self.port = port
|
|
||||||
|
|
||||||
def original_addr(self, sock):
|
|
||||||
return ("127.0.0.1", self.port)
|
|
||||||
|
|
||||||
|
|
||||||
class TransparentProxTest(ProxTestBase):
|
|
||||||
ssl = None
|
|
||||||
@classmethod
|
|
||||||
def get_proxy_config(cls):
|
|
||||||
return dict(
|
|
||||||
transparent_proxy = dict(
|
|
||||||
resolver = TResolver(cls.server.port),
|
|
||||||
sslports = []
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def pathod(self, spec):
|
|
||||||
"""
|
|
||||||
Constructs a pathod request, with the appropriate base and proxy.
|
|
||||||
"""
|
|
||||||
r = hurl.get(
|
|
||||||
"http://127.0.0.1:%s"%self.proxy.port + "/p/" + spec,
|
|
||||||
validate_cert=False,
|
|
||||||
#debug=hurl.utils.stdout_debug
|
|
||||||
)
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
class ReverseProxTest(ProxTestBase):
|
|
||||||
ssl = None
|
|
||||||
@classmethod
|
|
||||||
def get_proxy_config(cls):
|
|
||||||
return dict(
|
|
||||||
reverse_proxy = (
|
|
||||||
"https" if cls.ssl else "http",
|
|
||||||
"127.0.0.1",
|
|
||||||
cls.server.port
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def pathod(self, spec):
|
|
||||||
"""
|
|
||||||
Constructs a pathod request, with the appropriate base and proxy.
|
|
||||||
"""
|
|
||||||
r = hurl.get(
|
|
||||||
"http://127.0.0.1:%s"%self.proxy.port + "/p/" + spec,
|
|
||||||
validate_cert=False,
|
|
||||||
#debug=hurl.utils.stdout_debug
|
|
||||||
)
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def tmpdir(*args, **kwargs):
|
def tmpdir(*args, **kwargs):
|
||||||
orig_workdir = os.getcwd()
|
orig_workdir = os.getcwd()
|
||||||
@ -252,5 +90,4 @@ def raises(exc, obj, *args, **kwargs):
|
|||||||
)
|
)
|
||||||
raise AssertionError("No exception raised.")
|
raise AssertionError("No exception raised.")
|
||||||
|
|
||||||
|
|
||||||
test_data = utils.Data(__name__)
|
test_data = utils.Data(__name__)
|
||||||
|
Loading…
Reference in New Issue
Block a user