Edit on GitHub

#  Event Hooks

Addons hook into mitmproxy’s internal mechanisms through event hooks. These are implemented on addons as methods with a set of well-known names. Many events receive Flow objects as arguments - by modifying these objects, addons can change traffic on the fly. For instance, here is an addon that adds a response header with a count of the number of responses seen:

"""Add an HTTP header to each response."""


class AddHeader:
    def __init__(self):
        self.num = 0

    def response(self, flow):
        self.num = self.num + 1
        flow.response.headers["count"] = str(self.num)


addons = [
    AddHeader()
]
examples/addons/http-add-header.py

#  Available Hooks

The following addons list all available event hooks.

#   class LifecycleEvents:
View Source
class LifecycleEvents:
    """"""
    def load(self, loader: 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.
        """
        ctx.log(f"load: {loader=}")

    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.
        """
        ctx.log("running")

    def configure(self, updated: 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.
        """
        ctx.log(f"configure: {updated=}")

    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.
        """
        ctx.log("done")
#   def load(self, loader: mitmproxy.addonmanager.Loader):
View Source
    def load(self, loader: 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.
        """
        ctx.log(f"load: {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 running(self):
View Source
    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.
        """
        ctx.log("running")

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 configure(self, updated: Set[str]):
View Source
    def configure(self, updated: 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.
        """
        ctx.log(f"configure: {updated=}")

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):
View Source
    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.
        """
        ctx.log("done")

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.

#   class ConnectionEvents:
View Source
class ConnectionEvents:
    """"""
    def client_connected(self, client: mitmproxy.connection.Client):
        """
        A client has connected to mitmproxy. Note that a connection can
        correspond to multiple HTTP requests.

        Setting client.error kills the connection.
        """
        ctx.log(f"client_connected: {client=}")

    def client_disconnected(self, client: mitmproxy.connection.Client):
        """
        A client connection has been closed (either by us or the client).
        """
        ctx.log(f"client_disconnected: {client=}")

    def server_connect(self, data: mitmproxy.proxy.server_hooks.ServerConnectionHookData):
        """
        Mitmproxy is about to connect to a server.
        Note that a connection can correspond to multiple requests.

        Setting data.server.error kills the connection.
        """
        ctx.log(f"server_connect: {data=}")

    def server_connected(self, data: mitmproxy.proxy.server_hooks.ServerConnectionHookData):
        """
        Mitmproxy has connected to a server.
        """
        ctx.log(f"server_connected: {data=}")

    def server_disconnected(self, data: mitmproxy.proxy.server_hooks.ServerConnectionHookData):
        """
        A server connection has been closed (either by us or the server).
        """
        ctx.log(f"server_disconnected: {data=}")
#   def client_connected(self, client: mitmproxy.connection.Client):
View Source
    def client_connected(self, client: mitmproxy.connection.Client):
        """
        A client has connected to mitmproxy. Note that a connection can
        correspond to multiple HTTP requests.

        Setting client.error kills the connection.
        """
        ctx.log(f"client_connected: {client=}")

A client has connected to mitmproxy. Note that a connection can correspond to multiple HTTP requests.

Setting client.error kills the connection.

#   def client_disconnected(self, client: mitmproxy.connection.Client):
View Source
    def client_disconnected(self, client: mitmproxy.connection.Client):
        """
        A client connection has been closed (either by us or the client).
        """
        ctx.log(f"client_disconnected: {client=}")

A client connection has been closed (either by us or the client).

View Source
    def server_connect(self, data: mitmproxy.proxy.server_hooks.ServerConnectionHookData):
        """
        Mitmproxy is about to connect to a server.
        Note that a connection can correspond to multiple requests.

        Setting data.server.error kills the connection.
        """
        ctx.log(f"server_connect: {data=}")

Mitmproxy is about to connect to a server. Note that a connection can correspond to multiple requests.

Setting data.server.error kills the connection.

View Source
    def server_connected(self, data: mitmproxy.proxy.server_hooks.ServerConnectionHookData):
        """
        Mitmproxy has connected to a server.
        """
        ctx.log(f"server_connected: {data=}")

Mitmproxy has connected to a server.

#   def server_disconnected(self, data: mitmproxy.proxy.server_hooks.ServerConnectionHookData):
View Source
    def server_disconnected(self, data: mitmproxy.proxy.server_hooks.ServerConnectionHookData):
        """
        A server connection has been closed (either by us or the server).
        """
        ctx.log(f"server_disconnected: {data=}")

A server connection has been closed (either by us or the server).

#   class HTTPEvents:
View Source
class HTTPEvents:
    """"""
    def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
        """
        HTTP request headers were successfully read. At this point, the body is empty.
        """
        ctx.log(f"requestheaders: {flow=}")

    def request(self, flow: mitmproxy.http.HTTPFlow):
        """
        The full HTTP request has been read.

        Note: If request streaming is active, this event fires after the entire body has been streamed.
        HTTP trailers, if present, have not been transmitted to the server yet and can still be modified.
        Enabling streaming may cause unexpected event sequences: For example, `response` may now occur
        before `request` because the server replied with "413 Payload Too Large" during upload.
        """
        ctx.log(f"request: {flow=}")

    def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
        """
        HTTP response headers were successfully read. At this point, the body is empty.
        """
        ctx.log(f"responseheaders: {flow=}")

    def response(self, flow: mitmproxy.http.HTTPFlow):
        """
        The full HTTP response has been read.

        Note: If response streaming is active, this event fires after the entire body has been streamed.
        HTTP trailers, if present, have not been transmitted to the client yet and can still be modified.
        """
        ctx.log(f"response: {flow=}")

    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.

        Every flow will receive either an error or an response event, but not both.
        """
        ctx.log(f"error: {flow=}")

    def http_connect(self, flow: mitmproxy.http.HTTPFlow):
        """
        An HTTP CONNECT request was received. This event can be ignored for most practical purposes.

        This event only occurs in regular and upstream proxy modes
        when the client instructs mitmproxy to open a connection to an upstream host.
        Setting a non 2xx response on the flow will return the response to the client and abort the connection.

        CONNECT requests are HTTP proxy instructions for mitmproxy itself
        and not forwarded. They do not generate the usual HTTP handler events,
        but all requests going over the newly opened connection will.
        """
        ctx.log(f"http_connect: {flow=}")

    def http_connect_upstream(self, flow: mitmproxy.http.HTTPFlow):
        """
        An HTTP CONNECT request is about to be sent to an upstream proxy.
        This event can be ignored for most practical purposes.

        This event can be used to set custom authentication headers for upstream proxies.

        CONNECT requests do not generate the usual HTTP handler events,
        but all requests going over the newly opened connection will.
        """
        ctx.log(f"http_connect_upstream: {flow=}")
#   def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
        """
        HTTP request headers were successfully read. At this point, the body is empty.
        """
        ctx.log(f"requestheaders: {flow=}")

HTTP request headers were successfully read. At this point, the body is empty.

#   def request(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def request(self, flow: mitmproxy.http.HTTPFlow):
        """
        The full HTTP request has been read.

        Note: If request streaming is active, this event fires after the entire body has been streamed.
        HTTP trailers, if present, have not been transmitted to the server yet and can still be modified.
        Enabling streaming may cause unexpected event sequences: For example, `response` may now occur
        before `request` because the server replied with "413 Payload Too Large" during upload.
        """
        ctx.log(f"request: {flow=}")

The full HTTP request has been read.

Note: If request streaming is active, this event fires after the entire body has been streamed. HTTP trailers, if present, have not been transmitted to the server yet and can still be modified. Enabling streaming may cause unexpected event sequences: For example, response may now occur before request because the server replied with "413 Payload Too Large" during upload.

#   def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
        """
        HTTP response headers were successfully read. At this point, the body is empty.
        """
        ctx.log(f"responseheaders: {flow=}")

HTTP response headers were successfully read. At this point, the body is empty.

#   def response(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def response(self, flow: mitmproxy.http.HTTPFlow):
        """
        The full HTTP response has been read.

        Note: If response streaming is active, this event fires after the entire body has been streamed.
        HTTP trailers, if present, have not been transmitted to the client yet and can still be modified.
        """
        ctx.log(f"response: {flow=}")

The full HTTP response has been read.

Note: If response streaming is active, this event fires after the entire body has been streamed. HTTP trailers, if present, have not been transmitted to the client yet and can still be modified.

#   def error(self, flow: mitmproxy.http.HTTPFlow):
View Source
    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.

        Every flow will receive either an error or an response event, but not both.
        """
        ctx.log(f"error: {flow=}")

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.

Every flow will receive either an error or an response event, but not both.

#   def http_connect(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def http_connect(self, flow: mitmproxy.http.HTTPFlow):
        """
        An HTTP CONNECT request was received. This event can be ignored for most practical purposes.

        This event only occurs in regular and upstream proxy modes
        when the client instructs mitmproxy to open a connection to an upstream host.
        Setting a non 2xx response on the flow will return the response to the client and abort the connection.

        CONNECT requests are HTTP proxy instructions for mitmproxy itself
        and not forwarded. They do not generate the usual HTTP handler events,
        but all requests going over the newly opened connection will.
        """
        ctx.log(f"http_connect: {flow=}")

An HTTP CONNECT request was received. This event can be ignored for most practical purposes.

This event only occurs in regular and upstream proxy modes when the client instructs mitmproxy to open a connection to an upstream host. Setting a non 2xx response on the flow will return the response to the client and abort the connection.

CONNECT requests are HTTP proxy instructions for mitmproxy itself and not forwarded. They do not generate the usual HTTP handler events, but all requests going over the newly opened connection will.

#   def http_connect_upstream(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def http_connect_upstream(self, flow: mitmproxy.http.HTTPFlow):
        """
        An HTTP CONNECT request is about to be sent to an upstream proxy.
        This event can be ignored for most practical purposes.

        This event can be used to set custom authentication headers for upstream proxies.

        CONNECT requests do not generate the usual HTTP handler events,
        but all requests going over the newly opened connection will.
        """
        ctx.log(f"http_connect_upstream: {flow=}")

An HTTP CONNECT request is about to be sent to an upstream proxy. This event can be ignored for most practical purposes.

This event can be used to set custom authentication headers for upstream proxies.

CONNECT requests do not generate the usual HTTP handler events, but all requests going over the newly opened connection will.

#   class TCPEvents:
View Source
class TCPEvents:
    """"""
    def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
        """
        A TCP connection has started.
        """
        ctx.log(f"tcp_start: {flow=}")

    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.
        """
        ctx.log(f"tcp_message: {flow=}")

    def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
        """
        A TCP connection has ended.
        """
        ctx.log(f"tcp_end: {flow=}")

    def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
        """
        A TCP error has occurred.

        Every TCP flow will receive either a tcp_error or a tcp_end event, but not both.
        """
        ctx.log(f"tcp_error: {flow=}")
#   def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
View Source
    def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
        """
        A TCP connection has started.
        """
        ctx.log(f"tcp_start: {flow=}")

A TCP connection has started.

#   def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
View Source
    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.
        """
        ctx.log(f"tcp_message: {flow=}")

A TCP connection has received a message. The most recent message will be flow.messages[-1]. The message is user-modifiable.

#   def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
View Source
    def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
        """
        A TCP connection has ended.
        """
        ctx.log(f"tcp_end: {flow=}")

A TCP connection has ended.

#   def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
View Source
    def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
        """
        A TCP error has occurred.

        Every TCP flow will receive either a tcp_error or a tcp_end event, but not both.
        """
        ctx.log(f"tcp_error: {flow=}")

A TCP error has occurred.

Every TCP flow will receive either a tcp_error or a tcp_end event, but not both.

#   class TLSEvents:
View Source
class TLSEvents:
    """"""
    def tls_clienthello(self, data: mitmproxy.proxy.layers.tls.ClientHelloData):
        """
        Mitmproxy has received a TLS ClientHello message.

        This hook decides whether a server connection is needed
        to negotiate TLS with the client (data.establish_server_tls_first)
        """
        ctx.log(f"tls_clienthello: {data=}")

    def tls_start_client(self, data: mitmproxy.proxy.layers.tls.TlsStartData):
        """
        TLS Negotation between mitmproxy and a client is about to start.

        An addon is expected to initialize data.ssl_conn.
        (by default, this is done by mitmproxy.addons.TlsConfig)
        """
        ctx.log(f"tls_start_client: {data=}")

    def tls_start_server(self, data: mitmproxy.proxy.layers.tls.TlsStartData):
        """
        TLS Negotation between mitmproxy and a server is about to start.

        An addon is expected to initialize data.ssl_conn.
        (by default, this is done by mitmproxy.addons.TlsConfig)
        """
        ctx.log(f"tls_start_server: {data=}")
#   def tls_clienthello(self, data: mitmproxy.proxy.layers.tls.ClientHelloData):
View Source
    def tls_clienthello(self, data: mitmproxy.proxy.layers.tls.ClientHelloData):
        """
        Mitmproxy has received a TLS ClientHello message.

        This hook decides whether a server connection is needed
        to negotiate TLS with the client (data.establish_server_tls_first)
        """
        ctx.log(f"tls_clienthello: {data=}")

Mitmproxy has received a TLS ClientHello message.

This hook decides whether a server connection is needed to negotiate TLS with the client (data.establish_server_tls_first)

#   def tls_start_client(self, data: mitmproxy.proxy.layers.tls.TlsStartData):
View Source
    def tls_start_client(self, data: mitmproxy.proxy.layers.tls.TlsStartData):
        """
        TLS Negotation between mitmproxy and a client is about to start.

        An addon is expected to initialize data.ssl_conn.
        (by default, this is done by mitmproxy.addons.TlsConfig)
        """
        ctx.log(f"tls_start_client: {data=}")

TLS Negotation between mitmproxy and a client is about to start.

An addon is expected to initialize data.ssl_conn. (by default, this is done by mitmproxy.addons.TlsConfig)

#   def tls_start_server(self, data: mitmproxy.proxy.layers.tls.TlsStartData):
View Source
    def tls_start_server(self, data: mitmproxy.proxy.layers.tls.TlsStartData):
        """
        TLS Negotation between mitmproxy and a server is about to start.

        An addon is expected to initialize data.ssl_conn.
        (by default, this is done by mitmproxy.addons.TlsConfig)
        """
        ctx.log(f"tls_start_server: {data=}")

TLS Negotation between mitmproxy and a server is about to start.

An addon is expected to initialize data.ssl_conn. (by default, this is done by mitmproxy.addons.TlsConfig)

#   class WebSocketEvents:
View Source
class WebSocketEvents:
    """"""
    def websocket_start(self, flow: mitmproxy.http.HTTPFlow):
        """
        A WebSocket connection has commenced.
        """
        ctx.log(f"websocket_start: {flow=}")

    def websocket_message(self, flow: mitmproxy.http.HTTPFlow):
        """
        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.
        """
        ctx.log(f"websocket_message: {flow=}")

    def websocket_end(self, flow: mitmproxy.http.HTTPFlow):
        """
        A WebSocket connection has ended.
        You can check `flow.websocket.close_code` to determine why it ended.
        """
        ctx.log(f"websocket_end: {flow=}")
#   def websocket_start(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def websocket_start(self, flow: mitmproxy.http.HTTPFlow):
        """
        A WebSocket connection has commenced.
        """
        ctx.log(f"websocket_start: {flow=}")

A WebSocket connection has commenced.

#   def websocket_message(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def websocket_message(self, flow: mitmproxy.http.HTTPFlow):
        """
        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.
        """
        ctx.log(f"websocket_message: {flow=}")

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_end(self, flow: mitmproxy.http.HTTPFlow):
View Source
    def websocket_end(self, flow: mitmproxy.http.HTTPFlow):
        """
        A WebSocket connection has ended.
        You can check `flow.websocket.close_code` to determine why it ended.
        """
        ctx.log(f"websocket_end: {flow=}")

A WebSocket connection has ended. You can check flow.websocket.close_code to determine why it ended.

#   class AdvancedLifecycleEvents:
View Source
class AdvancedLifecycleEvents:
    """"""
    def next_layer(self, data: mitmproxy.proxy.layer.NextLayer):
        """
        Network layers are being switched. You may change which layer will be used by setting data.layer.

        (by default, this is done by mitmproxy.addons.NextLayer)
        """
        ctx.log(f"next_layer: {data=}")

    def update(self, flows: Sequence[mitmproxy.flow.Flow]):
        """
        Update is called when one or more flow objects have been modified,
        usually from a different addon.
        """
        ctx.log(f"update: {flows=}")

    def add_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!
        """
        ctx.log(f"add_log: {entry=}")
#   def next_layer(self, data: mitmproxy.proxy.layer.NextLayer):
View Source
    def next_layer(self, data: mitmproxy.proxy.layer.NextLayer):
        """
        Network layers are being switched. You may change which layer will be used by setting data.layer.

        (by default, this is done by mitmproxy.addons.NextLayer)
        """
        ctx.log(f"next_layer: {data=}")

Network layers are being switched. You may change which layer will be used by setting data.layer.

(by default, this is done by mitmproxy.addons.NextLayer)

#   def update(self, flows: Sequence[mitmproxy.flow.Flow]):
View Source
    def update(self, flows: Sequence[mitmproxy.flow.Flow]):
        """
        Update is called when one or more flow objects have been modified,
        usually from a different addon.
        """
        ctx.log(f"update: {flows=}")

Update is called when one or more flow objects have been modified, usually from a different addon.

#   def add_log(self, entry: mitmproxy.log.LogEntry):
View Source
    def add_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!
        """
        ctx.log(f"add_log: {entry=}")

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!