generate event documentation from source

This commit is contained in:
Maximilian Hils 2021-01-01 14:02:52 +01:00
parent f9d18745c9
commit 667cacba3f
8 changed files with 147 additions and 205 deletions

View File

@ -39,6 +39,8 @@ If you depend on these features, please raise your voice in
* New Proxy Core based on sans-io pattern (@mhils) * New Proxy Core based on sans-io pattern (@mhils)
* mitmproxy's command line interface now supports Windows (@mhils) * mitmproxy's command line interface now supports Windows (@mhils)
* The `clientconnect`, `clientdisconnect`, `serverconnect`, `serverdisconnect`, and `log`
events have been replaced with new events, see addon documentation for details (@mhils)
* Use pyca/cryptography to generate certificates, not pyOpenSSL (@mhils) * Use pyca/cryptography to generate certificates, not pyOpenSSL (@mhils)
* Remove the legacy protocol stack (@Kriechi) * Remove the legacy protocol stack (@Kriechi)
* Remove all deprecated pathod and pathoc tools and modules (@Kriechi) * Remove all deprecated pathod and pathoc tools and modules (@Kriechi)

141
docs/scripts/events.py Normal file
View File

@ -0,0 +1,141 @@
#!/usr/bin/env python3
import inspect
import textwrap
from typing import List, Type
import mitmproxy.addons.next_layer # noqa
from mitmproxy import events, log, addonmanager
from mitmproxy.proxy import server_hooks, layer
from mitmproxy.proxy.layers import http, tcp, tls, websocket
known = set()
def category(name: str, hooks: List[Type[events.MitmproxyEvent]]) -> None:
print(f"### {name} Events")
print("```python")
all_params = [
list(inspect.signature(hook.__init__).parameters.values())[1:]
for hook in hooks
]
# slightly overengineered, but this was fun to write. ¯\_(ツ)_/¯
imports = set()
types = set()
for params in all_params:
for param in params:
try:
mod = inspect.getmodule(param.annotation).__name__
if mod == "typing":
# this is ugly, but can be removed once we are on Python 3.9+ only
imports.add(inspect.getmodule(param.annotation.__args__[0]).__name__)
types.add(param.annotation._name)
else:
imports.add(mod)
except AttributeError:
raise ValueError(f"Missing type annotation: {params}")
imports.discard("builtins")
if types:
print(f"from typing import {', '.join(sorted(types))}")
print("from mitmproxy import ctx")
for imp in sorted(imports):
print(f"import {imp}")
print()
first = True
for hook, params in zip(hooks, all_params):
if first:
first = False
else:
print()
assert hook.name not in known
known.add(hook.name)
doc = inspect.getdoc(hook)
print(f"def {hook.name}({', '.join(str(p) for p in params)}):")
print(textwrap.indent(f'"""\n{doc}\n"""', " "))
if params:
print(f' ctx.log(f"{hook.name}: {" ".join("{" + p.name + "=}" for p in params)}")')
else:
print(f' ctx.log("{hook.name}")')
print("```")
category(
"Lifecycle",
[
addonmanager.LoadEvent,
events.RunningEvent,
events.ConfigureEvent,
events.DoneEvent,
]
)
category(
"Connection",
[
server_hooks.ClientConnectedHook,
server_hooks.ClientDisconnectedHook,
server_hooks.ServerConnectHook,
server_hooks.ServerConnectedHook,
server_hooks.ServerDisconnectedHook,
]
)
category(
"HTTP",
[
http.HttpRequestHeadersHook,
http.HttpRequestHook,
http.HttpResponseHeadersHook,
http.HttpResponseHook,
http.HttpErrorHook,
http.HttpConnectHook,
]
)
category(
"TCP",
[
tcp.TcpStartHook,
tcp.TcpMessageHook,
tcp.TcpEndHook,
tcp.TcpErrorHook,
]
)
category(
"TLS",
[
tls.TlsClienthelloHook,
tls.TlsStartHook,
]
)
category(
"WebSocket",
[
websocket.WebsocketStartHook,
websocket.WebsocketMessageHook,
websocket.WebsocketEndHook,
websocket.WebsocketErrorHook,
]
)
category(
"Advanced Lifecycle",
[
layer.NextLayerHook,
events.UpdateEvent,
log.AddLogEvent,
]
)
not_documented = set(events.all_events.keys()) - known
if not_documented:
raise RuntimeError(f"Not documented: {not_documented}")
# print("<table class=\"table filtertable\"><tbody>")
# for i in flowfilter.help:
# print("<tr><th>%s</th><td>%s</td></tr>" % i)
# print("</tbody></table>")

View File

@ -62,7 +62,7 @@ and adds a header to every request. The really interesting aspect of this
example is how users specify flows. Because mitmproxy can inspect the type example is how users specify flows. Because mitmproxy can inspect the type
signature, it can expand a text flow selector into a sequence of flows for us signature, it can expand a text flow selector into a sequence of flows for us
transparently. This means that the user has the full flexibility of [flow transparently. This means that the user has the full flexibility of [flow
filters]({{< relref addons-options >}}) available. Let's try it out. filters]({{< relref concepts-filters >}}) available. Let's try it out.
Start by loading the addon into mitmproxy and sending some traffic through so we Start by loading the addon into mitmproxy and sending some traffic through so we
have flows to work with: have flows to work with:

View File

@ -18,21 +18,7 @@ header with a count of the number of responses seen:
## Supported Events ## Supported Events
Below is an addon class that implements stubs for all events. We've added Below we list events supported by mitmproxy. We've added
annotations to illustrate the argument types for the various events. annotations to illustrate the argument types.
### Generic Events {{< readfile file="/generated/events.html" markdown="true" >}}
{{< example src="examples/addons/events.py" lang="py" >}}
### HTTP Events
{{< example src="examples/addons/events-http-specific.py" lang="py" >}}
### WebSocket Events
{{< example src="examples/addons/events-websocket-specific.py" lang="py" >}}
### TCP Events
{{< example src="examples/addons/events-tcp-specific.py" lang="py" >}}

View File

@ -1,42 +0,0 @@
"""HTTP-specific events."""
import mitmproxy.http
class Events:
def http_connect(self, flow: mitmproxy.http.HTTPFlow):
"""
An HTTP CONNECT request was received. Setting a non 2xx response on
the flow will return the response to the client abort the
connection. CONNECT requests and responses do not generate the usual
HTTP handler events. CONNECT requests are only valid in regular and
upstream proxy modes.
"""
def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
"""
HTTP request headers were successfully read. At this point, the body
is empty.
"""
def request(self, flow: mitmproxy.http.HTTPFlow):
"""
The full HTTP request has been read.
"""
def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
"""
HTTP response headers were successfully read. At this point, the body
is empty.
"""
def response(self, flow: mitmproxy.http.HTTPFlow):
"""
The full HTTP response has been read.
"""
def error(self, flow: mitmproxy.http.HTTPFlow):
"""
An HTTP error has occurred, e.g. invalid server responses, or
interrupted connections. This is distinct from a valid server HTTP
error response, which is simply a response with an HTTP error code.
"""

View File

@ -1,25 +0,0 @@
"""TCP-specific events."""
import mitmproxy.tcp
class Events:
def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
"""
A TCP connection has started.
"""
def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
"""
A TCP connection has received a message. The most recent message
will be flow.messages[-1]. The message is user-modifiable.
"""
def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
"""
A TCP error has occurred.
"""
def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
"""
A TCP connection has ended.
"""

View File

@ -1,37 +0,0 @@
"""WebSocket-specific events."""
import mitmproxy.http
import mitmproxy.websocket
class Events:
# WebSocket lifecycle
def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):
"""
Called when a client wants to establish a WebSocket connection. The
WebSocket-specific headers can be manipulated to alter the
handshake. The flow object is guaranteed to have a non-None request
attribute.
"""
def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):
"""
A WebSocket connection has commenced.
"""
def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
"""
Called when a WebSocket message is received from the client or
server. The most recent message will be flow.messages[-1]. The
message is user-modifiable. Currently there are two types of
messages, corresponding to the BINARY and TEXT frame types.
"""
def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
"""
A WebSocket connection has had an error.
"""
def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
"""
A WebSocket connection has ended.
"""

View File

@ -1,83 +0,0 @@
"""Generic event hooks."""
import typing
import mitmproxy.addonmanager
import mitmproxy.connections
import mitmproxy.log
import mitmproxy.proxy.protocol
class Events:
# Network lifecycle
def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):
"""
A client has connected to mitmproxy. Note that a connection can
correspond to multiple HTTP requests.
"""
def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):
"""
A client has disconnected from mitmproxy.
"""
def serverconnect(self, conn: mitmproxy.connections.ServerConnection):
"""
Mitmproxy has connected to a server. Note that a connection can
correspond to multiple requests.
"""
def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):
"""
Mitmproxy has disconnected from a server.
"""
def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):
"""
Network layers are being switched. You may change which layer will
be used by returning a new layer object from this event.
"""
# General lifecycle
def configure(self, updated: typing.Set[str]):
"""
Called when configuration changes. The updated argument is a
set-like object containing the keys of all changed options. This
event is called during startup with all options in the updated set.
"""
def done(self):
"""
Called when the addon shuts down, either by being removed from
the mitmproxy instance, or when mitmproxy itself shuts down. On
shutdown, this event is called after the event loop is
terminated, guaranteeing that it will be the final event an addon
sees. Note that log handlers are shut down at this point, so
calls to log functions will produce no output.
"""
def load(self, entry: mitmproxy.addonmanager.Loader):
"""
Called when an addon is first loaded. This event receives a Loader
object, which contains methods for adding options and commands. This
method is where the addon configures itself.
"""
def log(self, entry: mitmproxy.log.LogEntry):
"""
Called whenever a new log entry is created through the mitmproxy
context. Be careful not to log from this event, which will cause an
infinite loop!
"""
def running(self):
"""
Called when the proxy is completely up and running. At this point,
you can expect the proxy to be bound to a port, and all addons to be
loaded.
"""
def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):
"""
Update is called when one or more flow objects have been modified,
usually from a different addon.
"""