mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 00:01:36 +00:00
[sans-io] more http testing, more bugfixes!
This commit is contained in:
parent
fdcdb28251
commit
38f006eb9a
@ -2,7 +2,7 @@ import asyncio
|
|||||||
import warnings
|
import warnings
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mitmproxy import controller, ctx, eventsequence, log, master, options
|
from mitmproxy import controller, ctx, eventsequence, flow, log, master, options
|
||||||
from mitmproxy.flow import Error
|
from mitmproxy.flow import Error
|
||||||
from mitmproxy.proxy2 import commands
|
from mitmproxy.proxy2 import commands
|
||||||
from mitmproxy.proxy2 import server
|
from mitmproxy.proxy2 import server
|
||||||
@ -26,7 +26,7 @@ class AsyncReply(controller.Reply):
|
|||||||
|
|
||||||
def kill(self, force=False):
|
def kill(self, force=False):
|
||||||
warnings.warn("reply.kill() is deprecated, set the error attribute instead.", PendingDeprecationWarning)
|
warnings.warn("reply.kill() is deprecated, set the error attribute instead.", PendingDeprecationWarning)
|
||||||
self.obj.error = Error.KILLED_MESSAGE
|
self.obj.error = flow.Error(Error.KILLED_MESSAGE)
|
||||||
|
|
||||||
|
|
||||||
class ProxyConnectionHandler(server.StreamConnectionHandler):
|
class ProxyConnectionHandler(server.StreamConnectionHandler):
|
||||||
|
@ -192,6 +192,7 @@ class HttpStream(layer.Layer):
|
|||||||
if self.flow.request.stream:
|
if self.flow.request.stream:
|
||||||
if self.flow.response:
|
if self.flow.response:
|
||||||
raise NotImplementedError("Can't set a response and enable streaming at the same time.")
|
raise NotImplementedError("Can't set a response and enable streaming at the same time.")
|
||||||
|
yield HttpRequestHook(self.flow)
|
||||||
ok = yield from self.make_server_connection()
|
ok = yield from self.make_server_connection()
|
||||||
if not ok:
|
if not ok:
|
||||||
return
|
return
|
||||||
@ -266,6 +267,7 @@ class HttpStream(layer.Layer):
|
|||||||
yield SendHttp(ResponseData(self.stream_id, data), self.context.client)
|
yield SendHttp(ResponseData(self.stream_id, data), self.context.client)
|
||||||
elif isinstance(event, ResponseEndOfMessage):
|
elif isinstance(event, ResponseEndOfMessage):
|
||||||
self.flow.response.timestamp_end = time.time()
|
self.flow.response.timestamp_end = time.time()
|
||||||
|
yield HttpResponseHook(self.flow)
|
||||||
yield SendHttp(ResponseEndOfMessage(self.stream_id), self.context.client)
|
yield SendHttp(ResponseEndOfMessage(self.stream_id), self.context.client)
|
||||||
self.server_state = self.state_done
|
self.server_state = self.state_done
|
||||||
|
|
||||||
|
@ -3,29 +3,62 @@ from mitmproxy.proxy2 import commands
|
|||||||
|
|
||||||
|
|
||||||
class HttpRequestHeadersHook(commands.Hook):
|
class HttpRequestHeadersHook(commands.Hook):
|
||||||
|
"""
|
||||||
|
HTTP request headers were successfully read. At this point, the body is empty.
|
||||||
|
"""
|
||||||
name = "requestheaders"
|
name = "requestheaders"
|
||||||
flow: http.HTTPFlow
|
flow: http.HTTPFlow
|
||||||
|
|
||||||
|
|
||||||
class HttpRequestHook(commands.Hook):
|
class HttpRequestHook(commands.Hook):
|
||||||
|
"""
|
||||||
|
The full HTTP request has been read.
|
||||||
|
|
||||||
|
Note: This event fires immediately after requestheaders if the request body is streamed.
|
||||||
|
This ensures that requestheaders -> request -> responseheaders -> response happen in that order.
|
||||||
|
"""
|
||||||
name = "request"
|
name = "request"
|
||||||
flow: http.HTTPFlow
|
flow: http.HTTPFlow
|
||||||
|
|
||||||
|
|
||||||
class HttpResponseHook(commands.Hook):
|
|
||||||
name = "response"
|
|
||||||
flow: http.HTTPFlow
|
|
||||||
|
|
||||||
|
|
||||||
class HttpResponseHeadersHook(commands.Hook):
|
class HttpResponseHeadersHook(commands.Hook):
|
||||||
|
"""
|
||||||
|
The full HTTP response has been read.
|
||||||
|
"""
|
||||||
name = "responseheaders"
|
name = "responseheaders"
|
||||||
flow: http.HTTPFlow
|
flow: http.HTTPFlow
|
||||||
|
|
||||||
|
|
||||||
class HttpConnectHook(commands.Hook):
|
class HttpResponseHook(commands.Hook):
|
||||||
|
"""
|
||||||
|
HTTP response headers were successfully read. At this point, the body is empty.
|
||||||
|
|
||||||
|
Note: If response streaming is active, this event fires after the entire body has been streamed.
|
||||||
|
"""
|
||||||
|
name = "response"
|
||||||
flow: http.HTTPFlow
|
flow: http.HTTPFlow
|
||||||
|
|
||||||
|
|
||||||
class HttpErrorHook(commands.Hook):
|
class HttpErrorHook(commands.Hook):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
name = "error"
|
name = "error"
|
||||||
flow: http.HTTPFlow
|
flow: http.HTTPFlow
|
||||||
|
|
||||||
|
|
||||||
|
class HttpConnectHook(commands.Hook):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
flow: http.HTTPFlow
|
||||||
|
@ -233,8 +233,12 @@ def test_response_streaming(tctx):
|
|||||||
flow.response.stream = lambda x: x.upper()
|
flow.response.stream = lambda x: x.upper()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False)
|
Playbook(http.HttpLayer(tctx, HTTPMode.regular))
|
||||||
>> DataReceived(tctx.client, b"GET http://example.com/largefile HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
>> DataReceived(tctx.client, b"GET http://example.com/largefile HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||||
|
<< http.HttpRequestHeadersHook(flow)
|
||||||
|
>> reply()
|
||||||
|
<< http.HttpRequestHook(flow)
|
||||||
|
>> reply()
|
||||||
<< OpenConnection(server)
|
<< OpenConnection(server)
|
||||||
>> reply(None)
|
>> reply(None)
|
||||||
<< SendData(server, b"GET /largefile HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
<< SendData(server, b"GET /largefile HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||||
@ -244,6 +248,8 @@ def test_response_streaming(tctx):
|
|||||||
<< SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nABC")
|
<< SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nABC")
|
||||||
>> DataReceived(server, b"def")
|
>> DataReceived(server, b"def")
|
||||||
<< SendData(tctx.client, b"DEF")
|
<< SendData(tctx.client, b"DEF")
|
||||||
|
<< http.HttpResponseHook(flow)
|
||||||
|
>> reply()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -256,7 +262,7 @@ def test_request_streaming(tctx, response):
|
|||||||
"""
|
"""
|
||||||
server = Placeholder(Server)
|
server = Placeholder(Server)
|
||||||
flow = Placeholder(HTTPFlow)
|
flow = Placeholder(HTTPFlow)
|
||||||
playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False)
|
playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular))
|
||||||
|
|
||||||
def enable_streaming(flow: HTTPFlow):
|
def enable_streaming(flow: HTTPFlow):
|
||||||
flow.request.stream = lambda x: x.upper()
|
flow.request.stream = lambda x: x.upper()
|
||||||
@ -269,6 +275,8 @@ def test_request_streaming(tctx, response):
|
|||||||
b"abc")
|
b"abc")
|
||||||
<< http.HttpRequestHeadersHook(flow)
|
<< http.HttpRequestHeadersHook(flow)
|
||||||
>> reply(side_effect=enable_streaming)
|
>> reply(side_effect=enable_streaming)
|
||||||
|
<< http.HttpRequestHook(flow)
|
||||||
|
>> reply()
|
||||||
<< OpenConnection(server)
|
<< OpenConnection(server)
|
||||||
>> reply(None)
|
>> reply(None)
|
||||||
<< SendData(server, b"POST / HTTP/1.1\r\n"
|
<< SendData(server, b"POST / HTTP/1.1\r\n"
|
||||||
@ -282,6 +290,10 @@ def test_request_streaming(tctx, response):
|
|||||||
>> DataReceived(tctx.client, b"def")
|
>> DataReceived(tctx.client, b"def")
|
||||||
<< SendData(server, b"DEF")
|
<< SendData(server, b"DEF")
|
||||||
>> DataReceived(server, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
|
>> DataReceived(server, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
|
||||||
|
<< http.HttpResponseHeadersHook(flow)
|
||||||
|
>> reply()
|
||||||
|
<< http.HttpResponseHook(flow)
|
||||||
|
>> reply()
|
||||||
<< SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
|
<< SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
|
||||||
)
|
)
|
||||||
elif response == "early response":
|
elif response == "early response":
|
||||||
@ -291,17 +303,28 @@ def test_request_streaming(tctx, response):
|
|||||||
assert (
|
assert (
|
||||||
playbook
|
playbook
|
||||||
>> DataReceived(server, b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n")
|
>> DataReceived(server, b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n")
|
||||||
|
<< http.HttpResponseHeadersHook(flow)
|
||||||
|
>> reply()
|
||||||
|
<< http.HttpResponseHook(flow)
|
||||||
|
>> reply()
|
||||||
<< SendData(tctx.client, b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n")
|
<< SendData(tctx.client, b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n")
|
||||||
>> DataReceived(tctx.client, b"def")
|
>> DataReceived(tctx.client, b"def")
|
||||||
<< SendData(server, b"DEF")
|
<< SendData(server, b"DEF")
|
||||||
|
# Important: no request hook here!
|
||||||
)
|
)
|
||||||
elif response == "early close":
|
elif response == "early close":
|
||||||
assert (
|
assert (
|
||||||
playbook
|
playbook
|
||||||
>> DataReceived(server, b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n")
|
>> DataReceived(server, b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n")
|
||||||
|
<< http.HttpResponseHeadersHook(flow)
|
||||||
|
>> reply()
|
||||||
|
<< http.HttpResponseHook(flow)
|
||||||
|
>> reply()
|
||||||
<< SendData(tctx.client, b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n")
|
<< SendData(tctx.client, b"HTTP/1.1 413 Request Entity Too Large\r\nContent-Length: 0\r\n\r\n")
|
||||||
>> ConnectionClosed(server)
|
>> ConnectionClosed(server)
|
||||||
<< CloseConnection(server)
|
<< CloseConnection(server)
|
||||||
|
<< http.HttpErrorHook(flow)
|
||||||
|
>> reply()
|
||||||
<< CloseConnection(tctx.client)
|
<< CloseConnection(tctx.client)
|
||||||
)
|
)
|
||||||
elif response == "early kill":
|
elif response == "early kill":
|
||||||
@ -310,6 +333,8 @@ def test_request_streaming(tctx, response):
|
|||||||
playbook
|
playbook
|
||||||
>> ConnectionClosed(server)
|
>> ConnectionClosed(server)
|
||||||
<< CloseConnection(server)
|
<< CloseConnection(server)
|
||||||
|
<< http.HttpErrorHook(flow)
|
||||||
|
>> reply()
|
||||||
<< SendData(tctx.client, err)
|
<< SendData(tctx.client, err)
|
||||||
<< CloseConnection(tctx.client)
|
<< CloseConnection(tctx.client)
|
||||||
)
|
)
|
||||||
@ -640,6 +665,8 @@ def test_http_client_aborts(tctx, stream):
|
|||||||
assert (
|
assert (
|
||||||
playbook
|
playbook
|
||||||
>> reply(side_effect=enable_streaming)
|
>> reply(side_effect=enable_streaming)
|
||||||
|
<< http.HttpRequestHook(flow)
|
||||||
|
>> reply()
|
||||||
<< OpenConnection(server)
|
<< OpenConnection(server)
|
||||||
>> reply(None)
|
>> reply(None)
|
||||||
<< SendData(server, b"POST / HTTP/1.1\r\n"
|
<< SendData(server, b"POST / HTTP/1.1\r\n"
|
||||||
|
@ -102,24 +102,31 @@ def test_simple(tctx):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("stream", [True, False])
|
@pytest.mark.parametrize("stream", [True, False])
|
||||||
def test_http2_client_aborts(tctx, stream):
|
@pytest.mark.parametrize("when", ["request", "response"])
|
||||||
|
def test_http2_client_aborts(tctx, stream, when):
|
||||||
"""Test handling of the case where a client aborts during request transmission."""
|
"""Test handling of the case where a client aborts during request transmission."""
|
||||||
server = Placeholder(Server)
|
server = Placeholder(Server)
|
||||||
flow = Placeholder(HTTPFlow)
|
flow = Placeholder(HTTPFlow)
|
||||||
playbook, cff = start_h2_client(tctx)
|
playbook, cff = start_h2_client(tctx)
|
||||||
|
resp = Placeholder(bytes)
|
||||||
|
|
||||||
def enable_streaming(flow: HTTPFlow):
|
def enable_request_streaming(flow: HTTPFlow):
|
||||||
flow.request.stream = True
|
flow.request.stream = True
|
||||||
|
|
||||||
|
def enable_response_streaming(flow: HTTPFlow):
|
||||||
|
flow.response.stream = True
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
playbook
|
playbook
|
||||||
>> DataReceived(tctx.client, cff.build_headers_frame(example_request_headers).serialize())
|
>> DataReceived(tctx.client, cff.build_headers_frame(example_request_headers).serialize())
|
||||||
<< http.HttpRequestHeadersHook(flow)
|
<< http.HttpRequestHeadersHook(flow)
|
||||||
)
|
)
|
||||||
if stream:
|
if stream and when == "request":
|
||||||
assert (
|
assert (
|
||||||
playbook
|
playbook
|
||||||
>> reply(side_effect=enable_streaming)
|
>> reply(side_effect=enable_request_streaming)
|
||||||
|
<< http.HttpRequestHook(flow)
|
||||||
|
>> reply()
|
||||||
<< OpenConnection(server)
|
<< OpenConnection(server)
|
||||||
>> reply(None)
|
>> reply(None)
|
||||||
<< SendData(server, b"GET / HTTP/1.1\r\n"
|
<< SendData(server, b"GET / HTTP/1.1\r\n"
|
||||||
@ -127,16 +134,47 @@ def test_http2_client_aborts(tctx, stream):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert playbook >> reply()
|
assert playbook >> reply()
|
||||||
|
|
||||||
|
if when == "request":
|
||||||
|
assert (
|
||||||
|
playbook
|
||||||
|
>> ConnectionClosed(tctx.client)
|
||||||
|
<< CloseConnection(tctx.client)
|
||||||
|
<< http.HttpErrorHook(flow)
|
||||||
|
>> reply()
|
||||||
|
|
||||||
|
)
|
||||||
|
assert "peer closed connection" in flow().error.msg
|
||||||
|
return
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
playbook
|
playbook
|
||||||
>> ConnectionClosed(tctx.client)
|
>> DataReceived(tctx.client, cff.build_data_frame(b"", flags=["END_STREAM"]).serialize())
|
||||||
<< CloseConnection(tctx.client)
|
<< http.HttpRequestHook(flow)
|
||||||
<< http.HttpErrorHook(flow)
|
|
||||||
>> reply()
|
>> reply()
|
||||||
|
<< OpenConnection(server)
|
||||||
|
>> reply(None)
|
||||||
|
<< SendData(server, b"GET / HTTP/1.1\r\n"
|
||||||
|
b"Host: example.com\r\n\r\n")
|
||||||
|
>> DataReceived(server, b"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\n123")
|
||||||
|
<< http.HttpResponseHeadersHook(flow)
|
||||||
|
)
|
||||||
|
if stream:
|
||||||
|
assert (
|
||||||
|
playbook
|
||||||
|
>> reply(side_effect=enable_response_streaming)
|
||||||
|
<< SendData(tctx.client, resp)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
assert playbook >> reply()
|
||||||
|
assert (
|
||||||
|
playbook
|
||||||
|
>> ConnectionClosed(tctx.client)
|
||||||
|
<< CloseConnection(tctx.client)
|
||||||
|
>> DataReceived(server, b"456")
|
||||||
|
<< http.HttpResponseHook(flow)
|
||||||
|
>> reply()
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "peer closed connection" in flow().error.msg
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_normalization(tctx):
|
def test_no_normalization(tctx):
|
||||||
|
Loading…
Reference in New Issue
Block a user