streamline event/hook/command naming conventions

This commit is contained in:
Maximilian Hils 2021-01-05 08:17:40 +01:00
parent 90df4168f8
commit 6c0e4f1cb7
35 changed files with 171 additions and 170 deletions

View File

@ -4,14 +4,14 @@ import textwrap
from typing import List, Type
import mitmproxy.addons.next_layer # noqa
from mitmproxy import events, log, addonmanager
from mitmproxy import event_hooks, 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:
def category(name: str, hooks: List[Type[event_hooks.EventHook]]) -> None:
print(f"### {name} Events")
print("```python")
@ -65,10 +65,10 @@ def category(name: str, hooks: List[Type[events.MitmproxyEvent]]) -> None:
category(
"Lifecycle",
[
addonmanager.LoadEvent,
events.RunningEvent,
events.ConfigureEvent,
events.DoneEvent,
addonmanager.LoadEventHook,
event_hooks.RunningEventHook,
event_hooks.ConfigureEventHook,
event_hooks.DoneEventHook,
]
)
@ -127,12 +127,12 @@ category(
"Advanced Lifecycle",
[
layer.NextLayerHook,
events.UpdateEvent,
log.AddLogEvent,
event_hooks.UpdateEventHook,
log.AddLogEventHook,
]
)
not_documented = set(events.all_events.keys()) - known
not_documented = set(event_hooks.all_events.keys()) - known
if not_documented:
raise RuntimeError(f"Not documented: {not_documented}")

View File

@ -1,5 +1,5 @@
---
title: "Events"
title: "Event Hooks"
menu:
addons:
weight: 2

View File

@ -7,7 +7,7 @@ import typing
from dataclasses import dataclass
from mitmproxy import controller
from mitmproxy import events
from mitmproxy import event_hooks
from mitmproxy import exceptions
from mitmproxy import flow
from . import ctx
@ -112,7 +112,7 @@ def traverse(chain):
@dataclass
class LoadEvent(events.MitmproxyEvent):
class LoadEventHook(event_hooks.EventHook):
"""
Called when an addon is first loaded. This event receives a Loader
object, which contains methods for adding options and commands. This
@ -129,14 +129,14 @@ class AddonManager:
master.options.changed.connect(self._configure_all)
def _configure_all(self, options, updated):
self.trigger(events.ConfigureEvent(updated))
self.trigger(event_hooks.ConfigureEventHook(updated))
def clear(self):
"""
Remove all addons.
"""
for a in self.chain:
self.invoke_addon(a, events.DoneEvent())
self.invoke_addon(a, event_hooks.DoneEventHook())
self.lookup = {}
self.chain = []
@ -176,7 +176,7 @@ class AddonManager:
"An addon called '%s' already exists." % name
)
l = Loader(self.master)
self.invoke_addon(addon, LoadEvent(l))
self.invoke_addon(addon, LoadEventHook(l))
for a in traverse([addon]):
name = _get_name(a)
self.lookup[name] = a
@ -207,7 +207,7 @@ class AddonManager:
raise exceptions.AddonManagerError("No such addon: %s" % n)
self.chain = [i for i in self.chain if i is not a]
del self.lookup[_get_name(a)]
self.invoke_addon(addon, events.DoneEvent())
self.invoke_addon(addon, event_hooks.DoneEventHook())
def __len__(self):
return len(self.chain)
@ -219,7 +219,7 @@ class AddonManager:
name = _get_name(item)
return name in self.lookup
async def handle_lifecycle(self, event: events.MitmproxyEvent):
async def handle_lifecycle(self, event: event_hooks.EventHook):
"""
Handle a lifecycle event.
"""
@ -245,13 +245,13 @@ class AddonManager:
message.reply.mark_reset()
if isinstance(message, flow.Flow):
self.trigger(events.UpdateEvent([message]))
self.trigger(event_hooks.UpdateEventHook([message]))
def invoke_addon(self, addon, event: events.MitmproxyEvent):
def invoke_addon(self, addon, event: event_hooks.EventHook):
"""
Invoke an event on an addon and all its children.
"""
assert isinstance(event, events.MitmproxyEvent)
assert isinstance(event, event_hooks.EventHook)
for a in traverse([addon]):
func = getattr(a, event.name, None)
if func:
@ -268,7 +268,7 @@ class AddonManager:
f"Addon handler {event.name} ({a}) not callable"
)
def trigger(self, event: events.MitmproxyEvent):
def trigger(self, event: event_hooks.EventHook):
"""
Trigger an event across all addons.
"""

View File

@ -11,7 +11,7 @@ from mitmproxy import flow
from mitmproxy import http
from mitmproxy import io
from mitmproxy.addons.proxyserver import AsyncReply
from mitmproxy.events import UpdateEvent
from mitmproxy.event_hooks import UpdateEventHook
from mitmproxy.net import server_spec
from mitmproxy.options import Options
from mitmproxy.proxy.layers.http import HTTPMode
@ -85,7 +85,7 @@ class ReplayHandler(server.ConnectionHandler):
def log(self, message: str, level: str = "info") -> None:
ctx.log(f"[replay] {message}", level)
async def handle_hook(self, hook: commands.Hook) -> None:
async def handle_hook(self, hook: commands.StartHook) -> None:
data, = hook.args()
data.reply = AsyncReply(data)
await ctx.master.addons.handle_lifecycle(hook)
@ -185,7 +185,7 @@ class ClientPlayback:
f.revert()
updated.append(f)
ctx.master.addons.trigger(UpdateEvent(updated))
ctx.master.addons.trigger(UpdateEventHook(updated))
ctx.log.alert("Client replay queue cleared.")
@command.command("replay.client")
@ -209,7 +209,7 @@ class ClientPlayback:
http_flow.error = None
self.queue.put_nowait(http_flow)
updated.append(http_flow)
ctx.master.addons.trigger(UpdateEvent(updated))
ctx.master.addons.trigger(UpdateEventHook(updated))
@command.command("replay.client.file")
def load_file(self, path: mitmproxy.types.Path) -> None:

View File

@ -3,7 +3,7 @@ import typing
import os
from mitmproxy.utils import human
from mitmproxy import ctx, events
from mitmproxy import ctx, event_hooks
from mitmproxy import exceptions
from mitmproxy import command
from mitmproxy import flow
@ -99,7 +99,7 @@ class Core:
intercepted = [i for i in flows if i.intercepted]
for f in intercepted:
f.resume()
ctx.master.addons.trigger(events.UpdateEvent(intercepted))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(intercepted))
# FIXME: this will become view.mark later
@command.command("flow.mark")
@ -112,7 +112,7 @@ class Core:
if i.marked != boolean:
i.marked = boolean
updated.append(i)
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
# FIXME: this will become view.mark.toggle later
@command.command("flow.mark.toggle")
@ -122,7 +122,7 @@ class Core:
"""
for i in flows:
i.marked = not i.marked
ctx.master.addons.trigger(events.UpdateEvent(flows))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(flows))
@command.command("flow.kill")
def kill(self, flows: typing.Sequence[flow.Flow]) -> None:
@ -135,7 +135,7 @@ class Core:
f.kill()
updated.append(f)
ctx.log.alert("Killed %s flows." % len(updated))
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
# FIXME: this will become view.revert later
@command.command("flow.revert")
@ -149,7 +149,7 @@ class Core:
f.revert()
updated.append(f)
ctx.log.alert("Reverted %s flows." % len(updated))
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
@command.command("flow.set.options")
def flow_set_options(self) -> typing.Sequence[str]:
@ -218,7 +218,7 @@ class Core:
if rupdate or supdate:
updated.append(f)
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
ctx.log.alert("Set {} on {} flows.".format(attr, len(updated)))
@command.command("flow.decode")
@ -233,7 +233,7 @@ class Core:
f.backup()
p.decode()
updated.append(f)
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
ctx.log.alert("Decoded %s flows." % len(updated))
@command.command("flow.encode.toggle")
@ -252,7 +252,7 @@ class Core:
else:
p.decode()
updated.append(f)
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
ctx.log.alert("Toggled encoding on %s flows." % len(updated))
@command.command("flow.encode")
@ -275,7 +275,7 @@ class Core:
f.backup()
p.encode(encoding)
updated.append(f)
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
ctx.log.alert("Encoded %s flows." % len(updated))
@command.command("flow.encode.options")

View File

@ -40,7 +40,7 @@ class ProxyConnectionHandler(server.StreamConnectionHandler):
super().__init__(r, w, options)
self.log_prefix = f"{human.format_address(self.client.peername)}: "
async def handle_hook(self, hook: commands.Hook) -> None:
async def handle_hook(self, hook: commands.StartHook) -> None:
with self.timeout_watchdog.disarm():
# We currently only support single-argument hooks.
data, = hook.args()
@ -53,7 +53,7 @@ class ProxyConnectionHandler(server.StreamConnectionHandler):
x = log.LogEntry(self.log_prefix + message, level)
x.reply = controller.DummyReply() # type: ignore
asyncio_utils.create_task(
self.master.addons.handle_lifecycle(log.AddLogEvent(x)),
self.master.addons.handle_lifecycle(log.AddLogEventHook(x)),
name="ProxyConnectionHandler.log"
)

View File

@ -7,7 +7,7 @@ import types
import typing
import traceback
from mitmproxy import addonmanager, events
from mitmproxy import addonmanager, event_hooks
from mitmproxy import exceptions
from mitmproxy import flow
from mitmproxy import command
@ -104,11 +104,11 @@ class Script:
if self.ns:
# We're already running, so we have to explicitly register and
# configure the addon
ctx.master.addons.invoke_addon(self.ns, events.RunningEvent())
ctx.master.addons.invoke_addon(self.ns, event_hooks.RunningEventHook())
try:
ctx.master.addons.invoke_addon(
self.ns,
events.ConfigureEvent(ctx.options.keys())
event_hooks.ConfigureEventHook(ctx.options.keys())
)
except exceptions.OptionsError as e:
script_error_handler(self.fullpath, e, msg=str(e))
@ -160,10 +160,10 @@ class ScriptLoader:
mod = load_script(path)
if mod:
with addonmanager.safecall():
ctx.master.addons.invoke_addon(mod, events.RunningEvent())
ctx.master.addons.invoke_addon(mod, event_hooks.RunningEventHook())
ctx.master.addons.invoke_addon(
mod,
events.ConfigureEvent(ctx.options.keys()),
event_hooks.ConfigureEventHook(ctx.options.keys()),
)
for f in flows:
for evt in eventsequence.iterate(f):
@ -208,4 +208,4 @@ class ScriptLoader:
if self.is_running:
# If we're already running, we configure and tell the addon
# we're up and running.
ctx.master.addons.invoke_addon(s, events.RunningEvent())
ctx.master.addons.invoke_addon(s, event_hooks.RunningEventHook())

View File

@ -3,7 +3,7 @@ import typing
import urllib
import mitmproxy.types
from mitmproxy import command, events
from mitmproxy import command, event_hooks
from mitmproxy import ctx, http
from mitmproxy import exceptions
from mitmproxy import flow
@ -89,7 +89,7 @@ class ServerPlayback:
if isinstance(f, http.HTTPFlow):
lst = self.flowmap.setdefault(self._hash(f), [])
lst.append(f)
ctx.master.addons.trigger(events.UpdateEvent([]))
ctx.master.addons.trigger(event_hooks.UpdateEventHook([]))
@command.command("replay.server.file")
def load_file(self, path: mitmproxy.types.Path) -> None:
@ -105,7 +105,7 @@ class ServerPlayback:
Stop server replay.
"""
self.flowmap = {}
ctx.master.addons.trigger(events.UpdateEvent([]))
ctx.master.addons.trigger(event_hooks.UpdateEventHook([]))
@command.command("replay.server.count")
def count(self) -> int:

View File

@ -15,7 +15,7 @@ import blinker
import sortedcontainers
import mitmproxy.flow
from mitmproxy import flowfilter, events
from mitmproxy import flowfilter, event_hooks
from mitmproxy import exceptions
from mitmproxy import command
from mitmproxy import ctx
@ -381,7 +381,7 @@ class View(collections.abc.Sequence):
current = self.settings[f].get("key", "false")
self.settings[f][key] = "false" if current == "true" else "true"
updated.append(f)
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
@command.command("view.settings.setval")
def setvalue(
@ -396,7 +396,7 @@ class View(collections.abc.Sequence):
for f in flows:
self.settings[f][key] = value
updated.append(f)
ctx.master.addons.trigger(events.UpdateEvent(updated))
ctx.master.addons.trigger(event_hooks.UpdateEventHook(updated))
# Flows
@command.command("view.flows.duplicate")

View File

@ -9,7 +9,7 @@ if TYPE_CHECKING:
import mitmproxy.log
class MitmproxyEvent:
class EventHook:
name: ClassVar[str]
def args(self) -> List[Any]:
@ -19,7 +19,7 @@ class MitmproxyEvent:
return args
def __new__(cls, *args, **kwargs):
if cls is MitmproxyEvent:
if cls is EventHook:
raise TypeError("MitmproxyEvent may not be instantiated directly.")
if not is_dataclass(cls):
raise TypeError("Subclass is not a dataclass.")
@ -42,11 +42,11 @@ class MitmproxyEvent:
cls.__eq__ = object.__eq__
all_events: Dict[str, Type[MitmproxyEvent]] = {}
all_events: Dict[str, Type[EventHook]] = {}
@dataclass
class ConfigureEvent(MitmproxyEvent):
class ConfigureEventHook(EventHook):
"""
Called when configuration changes. The updated argument is a
set-like object containing the keys of all changed options. This
@ -56,7 +56,7 @@ class ConfigureEvent(MitmproxyEvent):
@dataclass
class DoneEvent(MitmproxyEvent):
class DoneEventHook(EventHook):
"""
Called when the addon shuts down, either by being removed from
the mitmproxy instance, or when mitmproxy itself shuts down. On
@ -68,7 +68,7 @@ class DoneEvent(MitmproxyEvent):
@dataclass
class RunningEvent(MitmproxyEvent):
class RunningEventHook(EventHook):
"""
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
@ -77,7 +77,7 @@ class RunningEvent(MitmproxyEvent):
@dataclass
class UpdateEvent(MitmproxyEvent):
class UpdateEventHook(EventHook):
"""
Update is called when one or more flow objects have been modified,
usually from a different addon.

View File

@ -1,14 +1,14 @@
from typing import Iterator, Any, Dict, Type, Callable
from mitmproxy import controller
from mitmproxy import events
from mitmproxy import event_hooks
from mitmproxy import flow
from mitmproxy import http
from mitmproxy import tcp
from mitmproxy import websocket
from mitmproxy.proxy import layers
TEventGenerator = Iterator[events.MitmproxyEvent]
TEventGenerator = Iterator[event_hooks.EventHook]
def _iterate_http(f: http.HTTPFlow) -> TEventGenerator:

View File

@ -1,7 +1,7 @@
import asyncio
from dataclasses import dataclass
from mitmproxy import events
from mitmproxy import event_hooks
class LogEntry:
@ -60,12 +60,12 @@ class Log:
def __call__(self, text, level="info"):
asyncio.get_event_loop().call_soon(
self.master.addons.trigger, AddLogEvent(LogEntry(text, level)),
self.master.addons.trigger, AddLogEventHook(LogEntry(text, level)),
)
@dataclass
class AddLogEvent(events.MitmproxyEvent):
class AddLogEventHook(event_hooks.EventHook):
"""
Called whenever a new log entry is created through the mitmproxy
context. Be careful not to log from this event, which will cause an

View File

@ -4,7 +4,7 @@ import sys
import threading
import traceback
from mitmproxy import addonmanager, events
from mitmproxy import addonmanager, event_hooks
from mitmproxy import command
from mitmproxy import controller
from mitmproxy import eventsequence
@ -45,7 +45,7 @@ class Master:
self.should_exit.clear()
async def running(self):
self.addons.trigger(events.RunningEvent())
self.addons.trigger(event_hooks.RunningEventHook())
def run_loop(self, loop):
self.start()
@ -71,7 +71,7 @@ class Master:
print("Please lodge a bug report at:", file=sys.stderr)
print("\thttps://github.com/mitmproxy/mitmproxy", file=sys.stderr)
self.addons.trigger(events.DoneEvent())
self.addons.trigger(event_hooks.DoneEventHook())
def run(self):
loop = asyncio.get_event_loop()

View File

@ -8,7 +8,7 @@ The counterpart to commands are events.
"""
from typing import Literal, Union, TYPE_CHECKING
import mitmproxy.events
import mitmproxy.event_hooks
from mitmproxy.proxy.context import Connection, Server
if TYPE_CHECKING:
@ -86,15 +86,16 @@ class CloseConnection(ConnectionCommand):
self.half_close = half_close
class Hook(Command, mitmproxy.events.MitmproxyEvent):
class StartHook(Command, mitmproxy.event_hooks.EventHook):
"""
Callback to the master (like ".ask()")
Start an event hook in the mitmproxy core.
This triggers a particular function (derived from the class name) in all addons.
"""
blocking = True
def __new__(cls, *args, **kwargs):
if cls is Hook:
raise TypeError("Hook may not be instantiated directly.")
if cls is StartHook:
raise TypeError("StartHook may not be instantiated directly.")
return super().__new__(cls, *args, **kwargs)

View File

@ -55,7 +55,7 @@ class ConnectionClosed(ConnectionEvent):
pass
class CommandReply(Event):
class CommandCompleted(Event):
"""
Emitted when a command has been finished, e.g.
when the master has replied or when we have established a server connection.
@ -64,8 +64,8 @@ class CommandReply(Event):
reply: typing.Any
def __new__(cls, *args, **kwargs):
if cls is CommandReply:
raise TypeError("CommandReply may not be instantiated directly.")
if cls is CommandCompleted:
raise TypeError("CommandCompleted may not be instantiated directly.")
assert is_dataclass(cls)
return super().__new__(cls)
@ -85,23 +85,23 @@ class CommandReply(Event):
return f"Reply({repr(self.command)})"
command_reply_subclasses: typing.Dict[commands.Command, typing.Type[CommandReply]] = {}
command_reply_subclasses: typing.Dict[commands.Command, typing.Type[CommandCompleted]] = {}
@dataclass(repr=False)
class OpenConnectionReply(CommandReply):
class OpenConnectionCompleted(CommandCompleted):
command: commands.OpenConnection
reply: typing.Optional[str]
"""error message"""
@dataclass(repr=False)
class HookReply(CommandReply):
command: commands.Hook
class HookCompleted(CommandCompleted):
command: commands.StartHook
reply: None = None
@dataclass(repr=False)
class GetSocketReply(CommandReply):
class GetSocketCompleted(CommandCompleted):
command: commands.GetSocket
reply: socket.socket

View File

@ -8,7 +8,7 @@ from dataclasses import dataclass
from typing import Optional, List, ClassVar, Deque, NamedTuple, Generator, Any, TypeVar
from mitmproxy.proxy import commands, events
from mitmproxy.proxy.commands import Command, Hook
from mitmproxy.proxy.commands import Command, StartHook
from mitmproxy.proxy.context import Connection, Context
T = TypeVar('T')
@ -35,8 +35,8 @@ class Layer:
Most layers only implement ._handle_event, which is called by the default implementation of .handle_event.
The default implementation allows layers to emulate blocking code:
When ._handle_event yields a command that has its blocking attribute set to True, .handle_event pauses
the execution of ._handle_event and waits until it is called with the corresponding CommandReply. All events
encountered in the meantime are buffered and replayed after execution is resumed.
the execution of ._handle_event and waits until it is called with the corresponding CommandCompleted event. All
events encountered in the meantime are buffered and replayed after execution is resumed.
The result is code that looks like blocking code, but is not blocking:
@ -96,13 +96,13 @@ class Layer:
if self._paused:
# did we just receive the reply we were waiting for?
pause_finished = (
isinstance(event, events.CommandReply) and
isinstance(event, events.CommandCompleted) and
event.command is self._paused.command
)
if self.debug is not None:
yield self.__debug(f"{'>>' if pause_finished else '>!'} {event}")
if pause_finished:
assert isinstance(event, events.CommandReply)
assert isinstance(event, events.CommandCompleted)
yield from self.__continue(event)
else:
self._paused_event_queue.append(event)
@ -142,7 +142,7 @@ class Layer:
except StopIteration:
return
def __continue(self, event: events.CommandReply):
def __continue(self, event: events.CommandCompleted):
"""continue processing events after being paused"""
assert self._paused is not None
command_generator = self._paused.generator
@ -241,7 +241,7 @@ class NextLayer(Layer):
@dataclass
class NextLayerHook(Hook):
class NextLayerHook(StartHook):
"""
Network layers are being switched. You may change which layer will be used by setting data.layer.

View File

@ -66,7 +66,7 @@ class GetHttpConnection(HttpCommand):
@dataclass
class GetHttpConnectionReply(events.CommandReply):
class GetHttpConnectionCompleted(events.CommandCompleted):
command: GetHttpConnection
reply: Union[Tuple[None, str], Tuple[Connection, None]]
"""connection object, error message"""
@ -554,7 +554,7 @@ class HttpLayer(layer.Layer):
yield from self.event_to_child(self.connections[self.context.client], event)
if self.mode is HTTPMode.upstream:
self.context.server.via = server_spec.parse_with_mode(self.context.options.mode)[1]
elif isinstance(event, events.CommandReply):
elif isinstance(event, events.CommandCompleted):
stream = self.command_sources.pop(event.command)
yield from self.event_to_child(stream, event)
elif isinstance(event, events.ConnectionEvent):
@ -574,7 +574,7 @@ class HttpLayer(layer.Layer):
) -> layer.CommandGenerator[None]:
for command in child.handle_event(event):
assert isinstance(command, commands.Command)
# Streams may yield blocking commands, which ultimately generate CommandReply events.
# Streams may yield blocking commands, which ultimately generate CommandCompleted events.
# Those need to be routed back to the correct stream, so we need to keep track of that.
if command.blocking:
@ -624,7 +624,7 @@ class HttpLayer(layer.Layer):
self.waiting_for_establishment[connection].append(event)
else:
stream = self.command_sources.pop(event)
yield from self.event_to_child(stream, GetHttpConnectionReply(event, (connection, None)))
yield from self.event_to_child(stream, GetHttpConnectionCompleted(event, (connection, None)))
return
can_use_context_connection = (
@ -674,7 +674,7 @@ class HttpLayer(layer.Layer):
for cmd in waiting:
stream = self.command_sources.pop(cmd)
yield from self.event_to_child(stream, GetHttpConnectionReply(cmd, reply))
yield from self.event_to_child(stream, GetHttpConnectionCompleted(cmd, reply))
# Somewhat ugly edge case: If we do HTTP/2 -> HTTP/1 proxying we don't want
# to handle everything over a single connection.

View File

@ -5,7 +5,7 @@ from mitmproxy.proxy import commands
@dataclass
class HttpRequestHeadersHook(commands.Hook):
class HttpRequestHeadersHook(commands.StartHook):
"""
HTTP request headers were successfully read. At this point, the body is empty.
"""
@ -14,7 +14,7 @@ class HttpRequestHeadersHook(commands.Hook):
@dataclass
class HttpRequestHook(commands.Hook):
class HttpRequestHook(commands.StartHook):
"""
The full HTTP request has been read.
@ -26,7 +26,7 @@ class HttpRequestHook(commands.Hook):
@dataclass
class HttpResponseHeadersHook(commands.Hook):
class HttpResponseHeadersHook(commands.StartHook):
"""
The full HTTP response has been read.
"""
@ -35,7 +35,7 @@ class HttpResponseHeadersHook(commands.Hook):
@dataclass
class HttpResponseHook(commands.Hook):
class HttpResponseHook(commands.StartHook):
"""
HTTP response headers were successfully read. At this point, the body is empty.
@ -46,7 +46,7 @@ class HttpResponseHook(commands.Hook):
@dataclass
class HttpErrorHook(commands.Hook):
class HttpErrorHook(commands.StartHook):
"""
An HTTP error has occurred, e.g. invalid server responses, or
interrupted connections. This is distinct from a valid server HTTP
@ -59,7 +59,7 @@ class HttpErrorHook(commands.Hook):
@dataclass
class HttpConnectHook(commands.Hook):
class HttpConnectHook(commands.StartHook):
"""
An HTTP CONNECT request was received. This event can be ignored for most practical purposes.

View File

@ -3,13 +3,13 @@ from typing import Optional
from mitmproxy import flow, tcp
from mitmproxy.proxy import commands, events, layer
from mitmproxy.proxy.commands import Hook
from mitmproxy.proxy.commands import StartHook
from mitmproxy.proxy.context import ConnectionState, Context, Connection
from mitmproxy.proxy.utils import expect
@dataclass
class TcpStartHook(Hook):
class TcpStartHook(StartHook):
"""
A TCP connection has started.
"""
@ -18,7 +18,7 @@ class TcpStartHook(Hook):
@dataclass
class TcpMessageHook(Hook):
class TcpMessageHook(StartHook):
"""
A TCP connection has received a message. The most recent message
will be flow.messages[-1]. The message is user-modifiable.
@ -27,7 +27,7 @@ class TcpMessageHook(Hook):
@dataclass
class TcpEndHook(Hook):
class TcpEndHook(StartHook):
"""
A TCP connection has ended.
"""
@ -35,7 +35,7 @@ class TcpEndHook(Hook):
@dataclass
class TcpErrorHook(Hook):
class TcpErrorHook(StartHook):
"""
A TCP error has occurred.

View File

@ -8,7 +8,7 @@ from mitmproxy import certs
from mitmproxy.net import tls as net_tls
from mitmproxy.proxy import commands, events, layer, tunnel
from mitmproxy.proxy import context
from mitmproxy.proxy.commands import Hook
from mitmproxy.proxy.commands import StartHook
from mitmproxy.utils import human
@ -104,7 +104,7 @@ class ClientHelloData:
@dataclass
class TlsClienthelloHook(Hook):
class TlsClienthelloHook(StartHook):
"""
Mitmproxy has received a TLS ClientHello message.
@ -122,7 +122,7 @@ class TlsStartData:
@dataclass
class TlsStartHook(Hook):
class TlsStartHook(StartHook):
"""
TLS Negotation is about to start.

View File

@ -7,7 +7,7 @@ import wsproto.frame_protocol
import wsproto.utilities
from mitmproxy import flow, websocket, http
from mitmproxy.proxy import commands, events, layer, context
from mitmproxy.proxy.commands import Hook
from mitmproxy.proxy.commands import StartHook
from mitmproxy.proxy.context import Context
from mitmproxy.proxy.utils import expect
from wsproto import ConnectionState
@ -15,7 +15,7 @@ from wsproto.frame_protocol import CloseReason, Opcode
@dataclass
class WebsocketStartHook(Hook):
class WebsocketStartHook(StartHook):
"""
A WebSocket connection has commenced.
"""
@ -23,7 +23,7 @@ class WebsocketStartHook(Hook):
@dataclass
class WebsocketMessageHook(Hook):
class WebsocketMessageHook(StartHook):
"""
Called when a WebSocket message is received from the client or
server. The most recent message will be flow.messages[-1]. The
@ -34,7 +34,7 @@ class WebsocketMessageHook(Hook):
@dataclass
class WebsocketEndHook(Hook):
class WebsocketEndHook(StartHook):
"""
A WebSocket connection has ended.
"""
@ -43,7 +43,7 @@ class WebsocketEndHook(Hook):
@dataclass
class WebsocketErrorHook(Hook):
class WebsocketErrorHook(StartHook):
"""
A WebSocket connection has had an error.

View File

@ -133,7 +133,7 @@ class ConnectionHandler(metaclass=abc.ABCMeta):
async def open_connection(self, command: commands.OpenConnection) -> None:
if not command.connection.address:
self.log(f"Cannot open connection, no hostname given.")
self.server_event(events.OpenConnectionReply(command, f"Cannot open connection, no hostname given."))
self.server_event(events.OpenConnectionCompleted(command, f"Cannot open connection, no hostname given."))
return
hook_data = server_hooks.ServerConnectionHookData(
@ -143,7 +143,7 @@ class ConnectionHandler(metaclass=abc.ABCMeta):
await self.handle_hook(server_hooks.ServerConnectHook(hook_data))
if command.connection.error:
self.log(f"server connection to {human.format_address(command.connection.address)} killed before connect.")
self.server_event(events.OpenConnectionReply(command, "Connection killed."))
self.server_event(events.OpenConnectionCompleted(command, "Connection killed."))
return
async with self.max_conns[command.connection.address]:
@ -156,7 +156,7 @@ class ConnectionHandler(metaclass=abc.ABCMeta):
err = "connection cancelled"
self.log(f"error establishing server connection: {err}")
command.connection.error = err
self.server_event(events.OpenConnectionReply(command, err))
self.server_event(events.OpenConnectionCompleted(command, err))
if isinstance(e, asyncio.CancelledError):
# From https://docs.python.org/3/library/asyncio-exceptions.html#asyncio.CancelledError:
# > In almost all situations the exception must be re-raised.
@ -184,7 +184,7 @@ class ConnectionHandler(metaclass=abc.ABCMeta):
if not connected_hook:
return # this should not be needed, see asyncio_utils.create_task
self.server_event(events.OpenConnectionReply(command, None))
self.server_event(events.OpenConnectionCompleted(command, None))
# during connection opening, this function is the designated handler that can be cancelled.
# once we have a connection, we do want the teardown here to happen in any case, so we
@ -256,13 +256,13 @@ class ConnectionHandler(metaclass=abc.ABCMeta):
assert handler
asyncio_utils.cancel_task(handler, "timeout")
async def hook_task(self, hook: commands.Hook) -> None:
async def hook_task(self, hook: commands.StartHook) -> None:
await self.handle_hook(hook)
if hook.blocking:
self.server_event(events.HookReply(hook))
self.server_event(events.HookCompleted(hook))
@abc.abstractmethod
async def handle_hook(self, hook: commands.Hook) -> None:
async def handle_hook(self, hook: commands.StartHook) -> None:
pass
def log(self, message: str, level: str = "info") -> None:
@ -294,8 +294,8 @@ class ConnectionHandler(metaclass=abc.ABCMeta):
writer = self.transports[command.connection].writer
assert writer
socket = writer.get_extra_info("socket")
self.server_event(events.GetSocketReply(command, socket))
elif isinstance(command, commands.Hook):
self.server_event(events.GetSocketCompleted(command, socket))
elif isinstance(command, commands.StartHook):
asyncio_utils.create_task(
self.hook_task(command),
name=f"handle_hook({command.name})",
@ -354,7 +354,7 @@ class SimpleConnectionHandler(StreamConnectionHandler): # pragma: no cover
async def handle_hook(
self,
hook: commands.Hook
hook: commands.StartHook
) -> None:
if hook.name in self.hook_handlers:
self.hook_handlers[hook.name](*hook.args())

View File

@ -4,7 +4,7 @@ from . import commands, context
@dataclass
class ClientConnectedHook(commands.Hook):
class ClientConnectedHook(commands.StartHook):
"""
A client has connected to mitmproxy. Note that a connection can
correspond to multiple HTTP requests.
@ -15,7 +15,7 @@ class ClientConnectedHook(commands.Hook):
@dataclass
class ClientDisconnectedHook(commands.Hook):
class ClientDisconnectedHook(commands.StartHook):
"""
A client connection has been closed (either by us or the client).
"""
@ -30,7 +30,7 @@ class ServerConnectionHookData:
@dataclass
class ServerConnectHook(commands.Hook):
class ServerConnectHook(commands.StartHook):
"""
Mitmproxy is about to connect to a server.
Note that a connection can correspond to multiple requests.
@ -41,7 +41,7 @@ class ServerConnectHook(commands.Hook):
@dataclass
class ServerConnectedHook(commands.Hook):
class ServerConnectedHook(commands.StartHook):
"""
Mitmproxy has connected to a server.
"""
@ -50,7 +50,7 @@ class ServerConnectedHook(commands.Hook):
@dataclass
class ServerDisconnectedHook(commands.Hook):
class ServerDisconnectedHook(commands.StartHook):
"""
A server connection has been closed (either by us or the server).
"""

View File

@ -89,7 +89,7 @@ class TunnelLayer(layer.Layer):
else:
self.tunnel_state = TunnelState.OPEN
if self.command_to_reply_to:
yield from self.event_to_child(events.OpenConnectionReply(self.command_to_reply_to, err))
yield from self.event_to_child(events.OpenConnectionCompleted(self.command_to_reply_to, err))
self.command_to_reply_to = None
else:
for evt in self._event_queue:
@ -117,7 +117,7 @@ class TunnelLayer(layer.Layer):
self.tunnel_state = TunnelState.ESTABLISHING
err = yield commands.OpenConnection(self.tunnel_connection)
if err:
yield from self.event_to_child(events.OpenConnectionReply(command, err))
yield from self.event_to_child(events.OpenConnectionCompleted(command, err))
self.tunnel_state = TunnelState.CLOSED
else:
yield from self.start_handshake()

View File

@ -3,7 +3,7 @@ This module provides a @concurrent decorator primitive to
offload computations from mitmproxy's main master thread.
"""
from mitmproxy import events
from mitmproxy import event_hooks
from mitmproxy.coretypes import basethread
@ -12,7 +12,7 @@ class ScriptThread(basethread.BaseThread):
def concurrent(fn):
if fn.__name__ not in set(events.all_events.keys()) - {"load", "configure"}:
if fn.__name__ not in set(event_hooks.all_events.keys()) - {"load", "configure"}:
raise NotImplementedError(
"Concurrent decorator not supported for '%s' method." % fn.__name__
)

View File

@ -4,7 +4,7 @@ import sys
import mitmproxy.master
import mitmproxy.options
from mitmproxy import addonmanager, events, log
from mitmproxy import addonmanager, event_hooks, log
from mitmproxy import command
from mitmproxy import eventsequence
from mitmproxy.addons import script, core
@ -14,8 +14,8 @@ class TestAddons(addonmanager.AddonManager):
def __init__(self, master):
super().__init__(master)
def trigger(self, event: events.MitmproxyEvent):
if isinstance(event, log.AddLogEvent):
def trigger(self, event: event_hooks.EventHook):
if isinstance(event, log.AddLogEventHook):
self.master.logs.append(event.entry)
super().trigger(event)
@ -106,7 +106,7 @@ class context:
if kwargs:
self.options.update(**kwargs)
else:
self.master.addons.invoke_addon(addon, events.ConfigureEvent(set()))
self.master.addons.invoke_addon(addon, event_hooks.ConfigureEventHook(set()))
def script(self, path):
"""
@ -115,7 +115,7 @@ class context:
sc = script.Script(path, False)
return sc.addons[0] if sc.addons else None
def invoke(self, addon, event: events.MitmproxyEvent):
def invoke(self, addon, event: event_hooks.EventHook):
"""
Recursively invoke an event on an addon and all its children.
"""

View File

@ -1,5 +1,5 @@
from mitmproxy import ctx
from mitmproxy import events
from mitmproxy import event_hooks
class Recorder:
@ -9,7 +9,7 @@ class Recorder:
self.name = name
def __getattr__(self, attr):
if attr in events.all_events:
if attr in event_hooks.all_events:
def prox(*args, **kwargs):
lg = (self.name, attr, args, kwargs)
if attr != "add_log":

View File

@ -2,7 +2,7 @@ from dataclasses import dataclass
import pytest
from mitmproxy.events import all_events
from mitmproxy.event_hooks import all_events
from mitmproxy.proxy import commands, context
@ -21,10 +21,10 @@ def test_dataclasses(tconn):
def test_hook():
with pytest.raises(TypeError):
commands.Hook()
commands.StartHook()
@dataclass
class TestHook(commands.Hook):
class TestHook(commands.StartHook):
data: bytes
f = TestHook(b"foo")

View File

@ -16,21 +16,21 @@ def test_dataclasses(tconn):
assert repr(events.ConnectionClosed(tconn))
def test_commandreply():
def test_command_completed():
with pytest.raises(TypeError):
events.CommandReply()
assert repr(events.HookReply(Mock(), None))
events.CommandCompleted()
assert repr(events.HookCompleted(Mock(), None))
class FooCommand(commands.Command):
pass
with pytest.raises(RuntimeError, match="properly annotated"):
class FooReply(events.CommandReply):
class FooCompleted(events.CommandCompleted):
pass
class FooReply1(events.CommandReply):
class FooCompleted1(events.CommandCompleted):
command: FooCommand
with pytest.raises(RuntimeError, match="conflicting subclasses"):
class FooReply2(events.CommandReply):
class FooCompleted2(events.CommandCompleted):
command: FooCommand

View File

@ -22,7 +22,7 @@ class TCommand(commands.Command):
@dataclass
class TCommandReply(events.CommandReply):
class TCommandCompleted(events.CommandCompleted):
command: TCommand

View File

@ -79,7 +79,7 @@ def _merge_sends(lst: typing.List[commands.Command], ignore_hooks: bool, ignore_
current_send.data += x.data
else:
ignore = (
(ignore_hooks and isinstance(x, commands.Hook))
(ignore_hooks and isinstance(x, commands.StartHook))
or
(ignore_logs and isinstance(x, commands.Log))
)
@ -191,7 +191,7 @@ class Playbook:
for name, value in vars(x).items():
if isinstance(value, _Placeholder):
setattr(x, name, value())
if isinstance(x, events.OpenConnectionReply) and not x.reply:
if isinstance(x, events.OpenConnectionCompleted) and not x.reply:
x.command.connection.state = ConnectionState.OPEN
elif isinstance(x, events.ConnectionClosed):
x.connection.state &= ~ConnectionState.CAN_READ
@ -227,13 +227,13 @@ class Playbook:
)
if need_to_emulate_log:
self.expected.insert(pos, cmd)
elif isinstance(cmd, commands.Hook) and not self.hooks:
elif isinstance(cmd, commands.StartHook) and not self.hooks:
need_to_emulate_hook = (
not self.hooks
and (
pos >= len(self.expected) or
(not (
isinstance(self.expected[pos], commands.Hook)
isinstance(self.expected[pos], commands.StartHook)
and self.expected[pos].name == cmd.name
))
)
@ -243,7 +243,7 @@ class Playbook:
if cmd.blocking:
# the current event may still have yielded more events, so we need to insert
# the reply *after* those additional events.
hook_replies.append(events.HookReply(cmd))
hook_replies.append(events.HookCompleted(cmd))
self.expected = self.expected[:pos + 1] + hook_replies + self.expected[pos + 1:]
eq(self.expected[i:], self.actual[i:]) # compare now already to set placeholders
@ -288,7 +288,7 @@ class reply(events.Event):
self.to = to
self.side_effect = side_effect
def playbook_eval(self, playbook: Playbook) -> events.CommandReply:
def playbook_eval(self, playbook: Playbook) -> events.CommandCompleted:
if isinstance(self.to, int):
expected = playbook.expected[:playbook.expected.index(self)]
assert abs(self.to) < len(expected)
@ -305,9 +305,9 @@ class reply(events.Event):
raise AssertionError(f"Expected command {self.to} did not occur.")
assert isinstance(self.to, commands.Command)
if isinstance(self.to, commands.Hook):
if isinstance(self.to, commands.StartHook):
self.side_effect(*self.to.args())
reply_cls = events.HookReply
reply_cls = events.HookCompleted
else:
self.side_effect(self.to)
reply_cls = command_reply_subclasses[type(self.to)]

View File

@ -3,7 +3,7 @@ from unittest import mock
import pytest
from mitmproxy import addonmanager
from mitmproxy import addons, events
from mitmproxy import addons, event_hooks
from mitmproxy import command
from mitmproxy import exceptions
from mitmproxy import master
@ -66,11 +66,11 @@ def test_halt():
a.add(end)
assert not end.running_called
a.trigger(events.RunningEvent())
a.trigger(event_hooks.RunningEventHook())
assert not end.running_called
a.remove(halt)
a.trigger(events.RunningEvent())
a.trigger(event_hooks.RunningEventHook())
assert end.running_called
@ -138,7 +138,7 @@ async def test_simple():
await tctx.master.await_log("AssertionError")
f = tflow.tflow()
a.trigger(events.RunningEvent())
a.trigger(event_hooks.RunningEventHook())
a.trigger(HttpResponseHook(f))
await tctx.master.await_log("not callable")
@ -153,7 +153,7 @@ async def test_simple():
ta = TAddon("one")
a.add(ta)
a.trigger(events.RunningEvent())
a.trigger(event_hooks.RunningEventHook())
assert ta.running_called
assert ta in a
@ -187,7 +187,7 @@ def test_nesting():
assert a.get("three")
assert a.get("four")
a.trigger(events.RunningEvent())
a.trigger(event_hooks.RunningEventHook())
assert a.get("one").running_called
assert a.get("two").running_called
assert a.get("three").running_called

View File

@ -2,35 +2,35 @@ from dataclasses import dataclass
import pytest
from mitmproxy import events
from mitmproxy import event_hooks
def test_event():
with pytest.raises(TypeError, match="may not be instantiated directly"):
events.MitmproxyEvent()
event_hooks.EventHook()
class NoDataClass(events.MitmproxyEvent):
class NoDataClass(event_hooks.EventHook):
pass
with pytest.raises(TypeError, match="not a dataclass"):
NoDataClass()
@dataclass
class FooEvent(events.MitmproxyEvent):
class FooEventHook(event_hooks.EventHook):
data: bytes
e = FooEvent(b"foo")
e = FooEventHook(b"foo")
assert repr(e)
assert e.args() == [b"foo"]
assert FooEvent in events.all_events.values()
assert FooEventHook in event_hooks.all_events.values()
with pytest.raises(RuntimeError, match="Two conflicting event classes"):
@dataclass
class FooEvent2(events.MitmproxyEvent):
class FooEventHook2(event_hooks.EventHook):
name = "foo"
@dataclass
class AnotherABC(events.MitmproxyEvent):
class AnotherABC(event_hooks.EventHook):
name = ""
assert AnotherABC not in events.all_events.values()
assert AnotherABC not in event_hooks.all_events.values()

View File

@ -2,7 +2,7 @@ import urwid
import pytest
from mitmproxy import options, events
from mitmproxy import options, event_hooks
from mitmproxy.tools import console
from ... import tservers
@ -13,7 +13,7 @@ class TestMaster(tservers.MasterTest):
def mkmaster(self, **opts):
o = options.Options(**opts)
m = console.master.ConsoleMaster(o)
m.addons.trigger(events.ConfigureEvent(o.keys()))
m.addons.trigger(event_hooks.ConfigureEventHook(o.keys()))
return m
async def test_basic(self):

View File

@ -18,7 +18,7 @@ class TestDumpMaster:
m = self.mkmaster()
ent = log.LogEntry("foo", "error")
ent.reply = controller.DummyReply()
m.addons.trigger(log.AddLogEvent(ent))
m.addons.trigger(log.AddLogEventHook(ent))
assert m.errorcheck.has_errored
@pytest.mark.parametrize("termlog", [False, True])