aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@nullcube.com>2016-10-16 11:12:58 +1300
committerAldo Cortesi <aldo@nullcube.com>2016-10-16 20:26:06 +1300
commitfb69c9c3453142ae6beb4040295accb41fdc6878 (patch)
tree91e4b36a9bdeeb801e1bfca6707f86bb4257f7a5
parent61040a7bcd46c057e34fe4671ef20b9111649e74 (diff)
downloadmitmproxy-fb69c9c3453142ae6beb4040295accb41fdc6878.tar.gz
mitmproxy-fb69c9c3453142ae6beb4040295accb41fdc6878.tar.bz2
mitmproxy-fb69c9c3453142ae6beb4040295accb41fdc6878.zip
docs: overview, classes, arguments
-rw-r--r--docs/scripting/api.rst8
-rw-r--r--docs/scripting/overview.rst72
-rw-r--r--examples/arguments.py17
-rw-r--r--examples/classes.py7
-rw-r--r--examples/modify_response_body.py21
-rw-r--r--mitmproxy/models/flow.py1
-rw-r--r--test/mitmproxy/test_examples.py7
7 files changed, 86 insertions, 47 deletions
diff --git a/docs/scripting/api.rst b/docs/scripting/api.rst
index a1769329..a864b442 100644
--- a/docs/scripting/api.rst
+++ b/docs/scripting/api.rst
@@ -8,6 +8,8 @@ API
- `mitmproxy.models.http.HTTPRequest <#mitmproxy.models.http.HTTPRequest>`_
- `mitmproxy.models.http.HTTPResponse <#mitmproxy.models.http.HTTPResponse>`_
- `mitmproxy.models.http.HTTPFlow <#mitmproxy.models.http.HTTPFlow>`_
+- Errors
+ - `mitmproxy.models.flow.Error <#mitmproxy.models.flow.Error>`_
HTTP
----
@@ -20,3 +22,9 @@ HTTP
.. autoclass:: mitmproxy.models.http.HTTPFlow
:inherited-members:
+
+Errors
+------
+
+.. autoclass:: mitmproxy.models.flow.Error
+ :inherited-members:
diff --git a/docs/scripting/overview.rst b/docs/scripting/overview.rst
index a0dfe111..f8dd9f2e 100644
--- a/docs/scripting/overview.rst
+++ b/docs/scripting/overview.rst
@@ -1,13 +1,17 @@
.. _overview:
-Overview
-=========
+Introduction
+============
Mitmproxy has a powerful scripting API that allows you to control almost any
aspect of traffic being proxied. In fact, much of mitmproxy's own core
functionality is implemented using the exact same API exposed to scripters (see
:src:`mitmproxy/builtins`).
+
+A simple example
+----------------
+
Scripting is event driven, with named handlers on the script object called at
appropriate points of mitmproxy's operation. Here's a complete mitmproxy script
that adds a new header to every HTTP response before it is returned to the
@@ -17,18 +21,57 @@ client:
:caption: :src:`examples/add_header.py`
:language: python
-All events that deal with an HTTP request get an instance of
-:py:class:`~mitmproxy.models.HTTPFlow`, which we can use to manipulate the
-response itself. We can now run this script using mitmdump or mitmproxy as
-follows:
+All events that deal with an HTTP request get an instance of `HTTPFlow
+<api.html#mitmproxy.models.http.HTTPFlow>`_, which we can use to manipulate the
+response itself. We can now run this script using mitmdump, and the new header
+will be added to all responses passing through the proxy:
>>> mitmdump -s add_header.py
-The new header will be added to all responses passing through the proxy.
+Using classes
+-------------
+
+In the example above, the script object is the ``add_header`` module itself.
+That is, the handlers are declared at the global level of the script. This is
+great for quick hacks, but soon becomes limiting as scripts become more
+sophisticated.
+
+When a script first starts up, the `start <events.html#start>`_, event is
+called before anything else happens. You can replace the current script object
+by returning it from this handler. Here's how this looks when applied to the
+example above:
+
+.. literalinclude:: ../../examples/classes.py
+ :caption: :src:`examples/classes.py`
+ :language: python
+
+So here, we're using a module-level script to "boot up" into a class instance.
+From this point on, the module-level script is removed from the handler chain,
+and is replaced by the class instance.
+
+
+Handling arguments
+------------------
-mitmproxy comes with a variety of example inline scripts, which demonstrate
-many basic tasks.
+Scripts can handle their own command-line arguments, just like any other Python
+program. Let's build on the example above to do something slightly more
+sophisticated - replace one value with another in all responses. Mitmproxy's
+`HTTPRequest <api.html#mitmproxy.models.http.HTTPRequest>`_ and `HTTPResponse
+<api.html#mitmproxy.models.http.HTTPResponse>`_ objects have a handy `replace
+<api.html#mitmproxy.models.http.HTTPResponse.replace>`_ method that takes care
+of all the details for us.
+
+.. literalinclude:: ../../examples/arguments.py
+ :caption: :src:`examples/arguments.py`
+ :language: python
+
+We can now call this script on the command-line like this:
+
+>>> mitmdump -dd -s "./arguments.py html faketml"
+
+Whenever a handler is called, mitpmroxy rewrites the script environment so that
+it sees its own arguments as if it was invoked from the command-line.
Running scripts in parallel
@@ -43,17 +86,6 @@ While that's usually a very desirable behaviour, blocking scripts can be run thr
:caption: examples/nonblocking.py
:language: python
-Make scripts configurable with arguments
-----------------------------------------
-
-Sometimes, you want to pass runtime arguments to the inline script. This can be simply done by
-surrounding the script call with quotes, e.g. ```mitmdump -s 'script.py --foo 42'``.
-The arguments are then exposed in the start event:
-
-.. literalinclude:: ../../examples/modify_response_body.py
- :caption: examples/modify_response_body.py
- :language: python
-
Running scripts on saved flows
------------------------------
diff --git a/examples/arguments.py b/examples/arguments.py
new file mode 100644
index 00000000..70851192
--- /dev/null
+++ b/examples/arguments.py
@@ -0,0 +1,17 @@
+import argparse
+
+
+class Replacer:
+ def __init__(self, src, dst):
+ self.src, self.dst = src, dst
+
+ def response(self, flow):
+ flow.response.replace(self.src, self.dst)
+
+
+def start():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("src", type=str)
+ parser.add_argument("dst", type=str)
+ args = parser.parse_args()
+ return Replacer(args.src, args.dst)
diff --git a/examples/classes.py b/examples/classes.py
new file mode 100644
index 00000000..6443798a
--- /dev/null
+++ b/examples/classes.py
@@ -0,0 +1,7 @@
+class AddHeader:
+ def response(self, flow):
+ flow.response.headers["newheader"] = "foo"
+
+
+def start():
+ return AddHeader()
diff --git a/examples/modify_response_body.py b/examples/modify_response_body.py
deleted file mode 100644
index b4632248..00000000
--- a/examples/modify_response_body.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Usage: mitmdump -s "modify_response_body.py mitmproxy bananas"
-# (this script works best with --anticache)
-import sys
-
-
-state = {}
-
-
-def start():
- if len(sys.argv) != 3:
- raise ValueError('Usage: -s "modify_response_body.py old new"')
- # You may want to use Python's argparse for more sophisticated argument
- # parsing.
- state["old"], state["new"] = sys.argv[1].encode(), sys.argv[2].encode()
-
-
-def response(flow):
- flow.response.content = flow.response.content.replace(
- state["old"],
- state["new"]
- )
diff --git a/mitmproxy/models/flow.py b/mitmproxy/models/flow.py
index 118aa3d1..165acfe7 100644
--- a/mitmproxy/models/flow.py
+++ b/mitmproxy/models/flow.py
@@ -24,7 +24,6 @@ class Error(stateobject.StateObject):
Exposes the following attributes:
- flow: Flow object
msg: Message describing the error
timestamp: Seconds since the epoch
"""
diff --git a/test/mitmproxy/test_examples.py b/test/mitmproxy/test_examples.py
index 1ba7ba7a..483cb228 100644
--- a/test/mitmproxy/test_examples.py
+++ b/test/mitmproxy/test_examples.py
@@ -87,11 +87,8 @@ class TestScripts(mastertest.MasterTest):
m.request(f)
assert f.request.query["mitmproxy"] == "rocks"
- def test_modify_response_body(self):
- with tutils.raises(ScriptError):
- tscript("modify_response_body.py")
-
- m, sc = tscript("modify_response_body.py", "mitmproxy rocks")
+ def test_arguments(self):
+ m, sc = tscript("arguments.py", "mitmproxy rocks")
f = tutils.tflow(resp=netutils.tresp(content=b"I <3 mitmproxy"))
m.response(f)
assert f.response.content == b"I <3 rocks"