mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 18:18:25 +00:00
Refactor reverse proxying
- Retain the specification from the Host header as a Request's description. - Expand upstream proxy specifications to include the scheme. We now say https://hostname:port - Move the "R" revert keybinding to "v" to make room for a reverse proxy binding that matches the command-line flag.
This commit is contained in:
parent
acdc2d00b4
commit
a7df6e1503
@ -66,7 +66,7 @@ def common_options(parser):
|
||||
)
|
||||
parser.add_option(
|
||||
"-R",
|
||||
action="store", dest="reverse_upstream", default=None,
|
||||
action="store", dest="reverse_proxy", default=None,
|
||||
help="Reverse proxy: upstream server host:port"
|
||||
)
|
||||
parser.add_option(
|
||||
|
@ -8,11 +8,11 @@ def _mkhelp():
|
||||
("a", "accept this intercepted connection"),
|
||||
("C", "clear connection list or eventlog"),
|
||||
("d", "delete flow"),
|
||||
("e", "toggle eventlog"),
|
||||
("l", "set limit filter pattern"),
|
||||
("L", "load saved flows"),
|
||||
("r", "replay request"),
|
||||
("R", "revert changes to request"),
|
||||
("v", "toggle eventlog"),
|
||||
("w", "save all flows matching current limit"),
|
||||
("W", "save this flow"),
|
||||
("X", "kill and delete connection, even if it's mid-intercept"),
|
||||
@ -133,8 +133,6 @@ class ConnectionItem(common.WWrap):
|
||||
)
|
||||
elif key == "X":
|
||||
self.flow.kill(self.master)
|
||||
elif key == "v":
|
||||
self.master.toggle_eventlog()
|
||||
elif key == "enter":
|
||||
if self.flow.request:
|
||||
self.master.view_flow(self.flow)
|
||||
@ -190,7 +188,7 @@ class ConnectionListBox(urwid.ListBox):
|
||||
elif key == "C":
|
||||
self.master.clear_connections()
|
||||
key = None
|
||||
elif key == "v":
|
||||
elif key == "e":
|
||||
self.master.toggle_eventlog()
|
||||
key = None
|
||||
return urwid.ListBox.keypress(self, size, key)
|
||||
|
@ -26,7 +26,7 @@ def _mkhelp():
|
||||
),
|
||||
("p", "previous flow"),
|
||||
("r", "replay request"),
|
||||
("R", "revert changes to request"),
|
||||
("V", "revert changes to request"),
|
||||
("v", "view body in external viewer"),
|
||||
("w", "save all flows matching current limit"),
|
||||
("W", "save this flow"),
|
||||
@ -513,7 +513,7 @@ class ConnectionView(common.WWrap):
|
||||
if r:
|
||||
self.master.statusbar.message(r)
|
||||
self.master.refresh_connection(self.flow)
|
||||
elif key == "R":
|
||||
elif key == "V":
|
||||
self.state.revert(self.flow)
|
||||
self.master.refresh_connection(self.flow)
|
||||
elif key == "W":
|
||||
|
@ -1292,9 +1292,9 @@ class FlowMaster(controller.Master):
|
||||
f.error = None
|
||||
self.process_new_request(f)
|
||||
rt = proxy.RequestReplayThread(
|
||||
self.server.config,
|
||||
f,
|
||||
self.masterq,
|
||||
self.server.config.body_size_limit
|
||||
)
|
||||
rt.start()
|
||||
#end nocover
|
||||
|
@ -2,8 +2,6 @@
|
||||
A simple proxy server implementation, which always reads all of a server
|
||||
response into memory, performs some transformation, and then writes it back
|
||||
to the client.
|
||||
|
||||
Development started from Neil Schemenauer's munchy.py
|
||||
"""
|
||||
import sys, os, string, socket, time
|
||||
import shutil, tempfile, threading
|
||||
@ -22,14 +20,14 @@ class ProxyError(Exception):
|
||||
|
||||
|
||||
class ProxyConfig:
|
||||
def __init__(self, certfile = None, ciphers = None, cacert = None, cert_wait_time=0, body_size_limit = None, reverse_upstream=None):
|
||||
def __init__(self, certfile = None, ciphers = None, cacert = None, cert_wait_time=0, body_size_limit = None, reverse_proxy=None):
|
||||
self.certfile = certfile
|
||||
self.ciphers = ciphers
|
||||
self.cacert = cacert
|
||||
self.certdir = None
|
||||
self.cert_wait_time = cert_wait_time
|
||||
self.body_size_limit = body_size_limit
|
||||
self.reverse_upstream = reverse_upstream
|
||||
self.reverse_proxy = reverse_proxy
|
||||
|
||||
|
||||
def read_chunked(fp, limit):
|
||||
@ -162,14 +160,14 @@ class FileLike:
|
||||
|
||||
#begin nocover
|
||||
class RequestReplayThread(threading.Thread):
|
||||
def __init__(self, flow, masterq, body_size_limit):
|
||||
self.flow, self.masterq, self.body_size_limit = flow, masterq, body_size_limit
|
||||
def __init__(self, config, flow, masterq):
|
||||
self.config, self.flow, self.masterq = config, flow, masterq
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
server = ServerConnection(self.flow.request, self.body_size_limit)
|
||||
server.send_request(self.flow.request)
|
||||
server = ServerConnection(self.config, self.flow.request)
|
||||
server.send()
|
||||
response = server.read_response()
|
||||
response._send(self.masterq)
|
||||
except ProxyError, v:
|
||||
@ -178,11 +176,14 @@ class RequestReplayThread(threading.Thread):
|
||||
|
||||
|
||||
class ServerConnection:
|
||||
def __init__(self, request, body_size_limit):
|
||||
self.body_size_limit = body_size_limit
|
||||
self.host = request.host
|
||||
self.port = request.port
|
||||
self.scheme = request.scheme
|
||||
def __init__(self, config, request):
|
||||
self.config, self.request = config, request
|
||||
if config.reverse_proxy:
|
||||
self.scheme, self.host, self.port = config.reverse_proxy
|
||||
else:
|
||||
self.host = request.host
|
||||
self.port = request.port
|
||||
self.scheme = request.scheme
|
||||
self.close = False
|
||||
self.server, self.rfile, self.wfile = None, None, None
|
||||
self.connect()
|
||||
@ -199,14 +200,13 @@ class ServerConnection:
|
||||
self.server = server
|
||||
self.rfile, self.wfile = server.makefile('rb'), server.makefile('wb')
|
||||
|
||||
def send_request(self, request):
|
||||
self.request = request
|
||||
request.close = self.close
|
||||
def send(self):
|
||||
self.request.close = self.close
|
||||
try:
|
||||
self.wfile.write(request._assemble())
|
||||
self.wfile.write(self.request._assemble())
|
||||
self.wfile.flush()
|
||||
except socket.error, err:
|
||||
raise ProxyError(502, 'Error sending data to "%s": %s' % (request.host, err))
|
||||
raise ProxyError(502, 'Error sending data to "%s": %s' % (self.request.host, err))
|
||||
|
||||
def read_response(self):
|
||||
line = self.rfile.readline()
|
||||
@ -231,7 +231,7 @@ class ServerConnection:
|
||||
if self.request.method == "HEAD" or code == 204 or code == 304:
|
||||
content = ""
|
||||
else:
|
||||
content = read_http_body(self.rfile, self, headers, True, self.body_size_limit)
|
||||
content = read_http_body(self.rfile, self, headers, True, self.config.body_size_limit)
|
||||
return flow.Response(self.request, code, msg, headers, content)
|
||||
|
||||
def terminate(self):
|
||||
@ -279,8 +279,8 @@ class ProxyHandler(SocketServer.StreamRequestHandler):
|
||||
request = False
|
||||
response = response._send(self.mqueue)
|
||||
else:
|
||||
server = ServerConnection(request, self.config.body_size_limit)
|
||||
server.send_request(request)
|
||||
server = ServerConnection(self.config, request)
|
||||
server.send()
|
||||
try:
|
||||
response = server.read_response()
|
||||
except IOError, v:
|
||||
@ -348,15 +348,6 @@ class ProxyHandler(SocketServer.StreamRequestHandler):
|
||||
self.rfile = FileLike(self.connection)
|
||||
self.wfile = FileLike(self.connection)
|
||||
method, scheme, host, port, path, httpminor = parse_request_line(self.rfile.readline())
|
||||
# If we're in reverse proxy mode, we only get the path and
|
||||
# version in the request and need to fill up host and port
|
||||
# from the configuration. This still assumes that the client will
|
||||
# provide the correct Host: header and we do not need to tamper
|
||||
# with that (or will tamper using other means).
|
||||
if self.config.reverse_upstream:
|
||||
scheme = 'http'
|
||||
host, port = self.config.reverse_upstream.split(':')
|
||||
port = int(port)
|
||||
if scheme is None:
|
||||
scheme = "https"
|
||||
headers = flow.Headers()
|
||||
@ -374,9 +365,12 @@ class ProxyHandler(SocketServer.StreamRequestHandler):
|
||||
port = 80
|
||||
port = int(port)
|
||||
if host is None:
|
||||
# FIXME: We only specify the first part of the invalid request in this error.
|
||||
# We should gather up everything read from the socket, and specify it all.
|
||||
raise ProxyError(400, 'Invalid request: %s'%line)
|
||||
if self.config.reverse_proxy:
|
||||
scheme, host, port = self.config.reverse_proxy
|
||||
else:
|
||||
# FIXME: We only specify the first part of the invalid request in this error.
|
||||
# We should gather up everything read from the socket, and specify it all.
|
||||
raise ProxyError(400, 'Invalid request: %s'%line)
|
||||
if "expect" in headers:
|
||||
expect = ",".join(headers['expect'])
|
||||
if expect == "100-continue" and httpminor >= 1:
|
||||
@ -493,13 +487,21 @@ def process_proxy_options(parser, options):
|
||||
if getattr(options, "cache", None) is not None:
|
||||
options.cache = os.path.expanduser(options.cache)
|
||||
body_size_limit = utils.parse_size(options.body_size_limit)
|
||||
|
||||
if options.reverse_proxy:
|
||||
rp = utils.parse_proxy_spec(options.reverse_proxy)
|
||||
if not rp:
|
||||
parser.error("Invalid reverse proxy specification: %s"%options.reverse_proxy)
|
||||
else:
|
||||
rp = None
|
||||
|
||||
return ProxyConfig(
|
||||
certfile = options.cert,
|
||||
cacert = cacert,
|
||||
ciphers = options.ciphers,
|
||||
cert_wait_time = options.cert_wait_time,
|
||||
body_size_limit = body_size_limit,
|
||||
reverse_upstream = options.reverse_upstream
|
||||
reverse_proxy = rp
|
||||
)
|
||||
|
||||
|
||||
|
@ -161,7 +161,6 @@ def del_all(dict, keys):
|
||||
del dict[key]
|
||||
|
||||
|
||||
|
||||
def pretty_size(size):
|
||||
suffixes = [
|
||||
("B", 2**10),
|
||||
@ -421,6 +420,13 @@ def parse_url(url):
|
||||
return scheme, host, port, path
|
||||
|
||||
|
||||
def parse_proxy_spec(url):
|
||||
p = parse_url(url)
|
||||
if not p:
|
||||
return None
|
||||
return p[:3]
|
||||
|
||||
|
||||
def clean_hanging_newline(t):
|
||||
"""
|
||||
Many editors will silently add a newline to the final line of a
|
||||
|
@ -200,6 +200,13 @@ class uLRUCache(libpry.AutoTree):
|
||||
assert len(f._cachelist_one) == 2
|
||||
|
||||
|
||||
class u_parse_proxy_spec(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
assert not utils.parse_proxy_spec("")
|
||||
assert utils.parse_proxy_spec("http://foo.com:88") == ("http", "foo.com", 88)
|
||||
assert utils.parse_proxy_spec("http://foo.com") == ("http", "foo.com", 80)
|
||||
assert not utils.parse_proxy_spec("foo.com")
|
||||
|
||||
|
||||
class u_parse_url(libpry.AutoTree):
|
||||
def test_simple(self):
|
||||
@ -254,6 +261,7 @@ tests = [
|
||||
udummy_cert(),
|
||||
uLRUCache(),
|
||||
u_parse_url(),
|
||||
u_parse_proxy_spec(),
|
||||
u_parse_size(),
|
||||
uclean_hanging_newline()
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user