Skip to content

Mark HTTP/2 flows imported from HAR files as HTTP/2 - #8583

Open
JayOfTheKeyboard wants to merge 1 commit into
mitmproxy:mainfrom
JayOfTheKeyboard:har-import-http2-version
Open

JayOfTheKeyboard wants to merge 1 commit into
mitmproxy:mainfrom
JayOfTheKeyboard:har-import-http2-version

Conversation

@JayOfTheKeyboard

Copy link
Copy Markdown

Description

The HAR importer stores HTTP/2 as "HTTP/2", but Message.is_http2 and the HTTP/2 layer use "HTTP/2.0". So HTTP/2 flows loaded from a browser HAR (Chrome writes http/2.0, Safari writes HTTP/2) have is_http2 == False. A HAR exported by mitmproxy itself writes "HTTP/2.0", which matches none of the cases and comes back as HTTP/1.1.

It shows up in client replay. With is_http2 False, the HTTP/1 layer skips its HTTP/2 to HTTP/1 conversion and puts the version straight into the request line. With a HAR entry that has "httpVersion": "http/2.0" and a plain python -m http.server as the target:

$ mitmdump -C h2.har
[replay]: GET http://127.0.0.1:18779/hello HTTP/2
 << HTTP/1.0 400 Bad request version ('HTTP/2') 487b

The server receives GET /hello HTTP/2. With this change the request goes out as GET /hello HTTP/1.1 and gets a 200.

This maps http/2.0, HTTP/2 and HTTP/2.0 to HTTP/2.0 for both the request and the response. The expected http_version in five HAR fixtures changes to match (head-content-length.har was exported with HTTP/2.0, so it used to load as HTTP/1.1).

Chrome HARs also list the HTTP/2 pseudo-headers (:authority, :method, ...) as ordinary headers, and the importer keeps them, so replaying a Chrome entry still fails, on main and on this branch alike, with Received invalid header name: b':authority'. I left that for a separate PR, along with Chrome's h3 version string, which still imports as HTTP/1.1. I can send either.

#8535 edits the same match blocks for HTTP/1.0. The two changes don't depend on each other, and whichever lands second needs a one-line rebase.

Testing:

  • New test_http2_version checks is_http2 on both sides for all three spellings. It fails on main with assert False where False = Request(GET address:22/path).is_http2.
  • The replay above, before and after.
  • tox -e py,lint,mypy,filename_matching passes on main and on this branch (2026 vs 2029 passed, the 3 new tests), and individual_coverage is ok for mitmproxy/io/har.py.

Checklist

  • I have updated tests where applicable.
  • I have added an entry to the CHANGELOG.

The HAR importer stored HTTP/2 versions as "HTTP/2", but mitmproxy
uses "HTTP/2.0" everywhere else, so is_http2 was False for every
imported HTTP/2 flow. mitmproxy's own HAR export writes "HTTP/2.0",
which matched no case at all and came back as HTTP/1.1.

With is_http2 False, client replay of an imported HTTP/2 flow over an
HTTP/1 connection skipped the HTTP/2 -> HTTP/1 conversion and sent
"GET /path HTTP/2" as the request line, which servers reject.
@JayOfTheKeyboard
JayOfTheKeyboard force-pushed the har-import-http2-version branch from c8d3d70 to 4b8ddf1 Compare November 9, 2026 06:50

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant