Repository: mitmproxy/mitmproxy

Issue:
Replay does not work with HTTP/2 flows.
##### Steps to reproduce the problem:

1. Replay a HTTP/2 flow.
2. Notice that the server fails because it expects a `host` header, but we only send an `:authority` header.

##### System information

Mitmproxy version: 3.0.0 (2.0.0dev0229-0x0c0c0d3)
Python version: 3.5.2
Platform: Linux-3.4.0+-x86_64-with-Ubuntu-14.04-trusty
SSL version: OpenSSL 1.0.2g-fips  1 Mar 2016
Linux distro: Ubuntu 14.04 trusty



Discussion:
Are you trying to replay an HTTP/2 request against a HTTP/1 server?
If we would want to support this mixed-protocol-proxying, I guess we have to emulate/set that header (and possible others as well?)
Not trying intentionally, but that's what currently happens if you press `r` on a HTTP/2.0 flow. So I'm not necessarily saying we have to translate headers, we could also do the replay using HTTP/2.0. Not sure what's best, but we should do one of those things. :)
As discussed, the way to fix this is by always replaying via a fresh HTTP/1 connection and setting the correct headers: renaming `:authority` to `Host` (value should be the same), and changing `http_version` of the request.

Resolving patch:
diff --git a/mitmproxy/master.py b/mitmproxy/master.py
--- a/mitmproxy/master.py
+++ b/mitmproxy/master.py
@@ -162,6 +162,11 @@ def replay_request(
         f.response = None
         f.error = None
 
+        if f.request.http_version == "HTTP/2.0":  # https://github.com/mitmproxy/mitmproxy/issues/2197
+            f.request.http_version = "HTTP/1.1"
+            host = f.request.headers.pop(":authority")
+            f.request.headers.insert(0, "host", host)
+
         rt = http_replay.RequestReplayThread(
             self.server.config,
             f,


Test patch:
diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py
--- a/test/mitmproxy/test_flow.py
+++ b/test/mitmproxy/test_flow.py
@@ -1,7 +1,8 @@
 import io
+from unittest import mock
 import pytest
 
-from mitmproxy.test import tflow
+from mitmproxy.test import tflow, tutils
 import mitmproxy.io
 from mitmproxy import flowfilter
 from mitmproxy import options
@@ -10,6 +11,7 @@
 from mitmproxy.exceptions import FlowReadException, ReplayException, ControlException
 from mitmproxy import flow
 from mitmproxy import http
+from mitmproxy.net import http as net_http
 from mitmproxy.proxy.server import DummyServer
 from mitmproxy import master
 from . import tservers
@@ -99,7 +101,9 @@ def test_load_flow_reverse(self):
         assert s.flows[0].request.host == "use-this-domain"
 
     def test_replay(self):
-        fm = master.Master(None, DummyServer())
+        opts = options.Options()
+        conf = config.ProxyConfig(opts)
+        fm = master.Master(opts, DummyServer(conf))
         f = tflow.tflow(resp=True)
         f.request.content = None
         with pytest.raises(ReplayException, match="missing"):
@@ -117,6 +121,14 @@ def test_replay(self):
         with pytest.raises(ReplayException, match="live"):
             fm.replay_request(f)
 
+        req = tutils.treq(headers=net_http.Headers(((b":authority", b"foo"), (b"header", b"qvalue"), (b"content-length", b"7"))))
+        f = tflow.tflow(req=req)
+        f.request.http_version = "HTTP/2.0"
+        with mock.patch('mitmproxy.proxy.protocol.http_replay.RequestReplayThread.run'):
+            rt = fm.replay_request(f)
+            assert rt.f.request.http_version == "HTTP/1.1"
+            assert ":authority" not in rt.f.request.headers
+
     def test_all(self):
         s = tservers.TestState()
         fm = master.Master(None, DummyServer())