)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"4de5ab051d58a889d0d2305a9a1fff6f6361ba3d","unresolved":true,"context_lines":[{"line_number":6,"context_line":""},{"line_number":7,"context_line":"Fix unicode-header-name handling"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"There were two fixes needed:"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"- bufferedhttp needed to re-assess message framing if/when it needed to"},{"line_number":12,"context_line":"  parse additional headers (in case they included transfer-encoding /"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":2,"id":"7ae65220_1c1c2d35","line":9,"range":{"start_line":9,"start_character":0,"end_line":9,"end_character":28},"updated":"2025-01-10 10:35:49.000000000","message":"Jianjian mentions a cpython bug - it\u0027s be worth mentioning that here.\n\nOh, but the code already mentions https://bugs.python.org/issue37093 so is it that the original method override didn\u0027t fix everything and this is now improving on that? If so can we reference the original patch  I0f03c211f35a9a49e047a5718a9907b515ca88d7","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"306b321c2e134c7afe0221a4ea9adf8998142beb","unresolved":true,"context_lines":[{"line_number":6,"context_line":""},{"line_number":7,"context_line":"Fix unicode-header-name handling"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"There were two fixes needed:"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"- bufferedhttp needed to re-assess message framing if/when it needed to"},{"line_number":12,"context_line":"  parse additional headers (in case they included transfer-encoding /"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":2,"id":"89341391_b806475f","line":9,"range":{"start_line":9,"start_character":0,"end_line":9,"end_character":28},"in_reply_to":"7ae65220_1c1c2d35","updated":"2025-01-10 17:13:25.000000000","message":"Good call -- will add a `Related-Change:`","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"7fd640607d9ec2fc742f9c4fd93a47b40bff58dd","unresolved":false,"context_lines":[{"line_number":6,"context_line":""},{"line_number":7,"context_line":"Fix unicode-header-name handling"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"There were two fixes needed:"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"- bufferedhttp needed to re-assess message framing if/when it needed to"},{"line_number":12,"context_line":"  parse additional headers (in case they included transfer-encoding /"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":2,"id":"6fd0fff0_bc83146b","line":9,"range":{"start_line":9,"start_character":0,"end_line":9,"end_character":28},"in_reply_to":"89341391_b806475f","updated":"2025-01-14 20:04:50.000000000","message":"Done","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"4de5ab051d58a889d0d2305a9a1fff6f6361ba3d","unresolved":true,"context_lines":[{"line_number":13,"context_line":"  content-length)"},{"line_number":14,"context_line":"- internal_client needed to encode to bytes before .lower()ing metadata"},{"line_number":15,"context_line":""},{"line_number":16,"context_line":"Now, TestReconstructorRebuildUTF8 can pass on py3 (though maybe not"},{"line_number":17,"context_line":"reliably??)"},{"line_number":18,"context_line":""},{"line_number":19,"context_line":"Change-Id: I6aa16fda9285c9fc3816da6fbff2615bd14a020c"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":2,"id":"d5aa714e_d76ec035","line":17,"range":{"start_line":16,"start_character":51,"end_line":17,"end_character":8},"updated":"2025-01-10 10:35:49.000000000","message":"so we shouldn\u0027t merge this ? ;-) I can\u0027t see any note in the test to suggest where it might be flakey, so this is a bit cryptic.","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"7fd640607d9ec2fc742f9c4fd93a47b40bff58dd","unresolved":false,"context_lines":[{"line_number":13,"context_line":"  content-length)"},{"line_number":14,"context_line":"- internal_client needed to encode to bytes before .lower()ing metadata"},{"line_number":15,"context_line":""},{"line_number":16,"context_line":"Now, TestReconstructorRebuildUTF8 can pass on py3 (though maybe not"},{"line_number":17,"context_line":"reliably??)"},{"line_number":18,"context_line":""},{"line_number":19,"context_line":"Change-Id: I6aa16fda9285c9fc3816da6fbff2615bd14a020c"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":2,"id":"cb0a2188_b6f0c7dd","line":17,"range":{"start_line":16,"start_character":51,"end_line":17,"end_character":8},"in_reply_to":"d5aa714e_d76ec035","updated":"2025-01-14 20:04:50.000000000","message":"the flaky failure is not related to this patch, I could reproduce it with master branch as well.\n```\nset -e\n\nfor i in $(seq 100); do\n    pytest swift/test/probe/test_reconstructor_rebuild.py::TestReconstructorRebuild::test_sync_expired_object\ndone\n...\nE           swiftclient.exceptions.ClientException: Object GET failed: http://saio:8080/v1/AUTH_test/container-11103288-d78a-41c8-8c72-6c7d82ce14e1/object-a05922ae-876d-4a33-aef7-e3b3eda72e42 503 Service Unavailable  [first 60 chars of response] b\u0027\u003chtml\u003e\u003ch1\u003eService Unavailable\u003c/h1\u003e\u003cp\u003eThe server is currently\u0027 (txn: tx12d66b1cfaf74bdd9e040-0067855fce)\n```","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"5fb03989da4e77b99c0a9ff883461e8b067bbfb8","unresolved":true,"context_lines":[{"line_number":8,"context_line":""},{"line_number":9,"context_line":"There were two fixes needed:"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"- bufferedhttp needed to re-assess message framing if/when it needed to"},{"line_number":12,"context_line":"  parse additional headers (in case they included transfer-encoding /"},{"line_number":13,"context_line":"  content-length)"},{"line_number":14,"context_line":"- internal_client needed to encode to bytes before .lower()ing metadata"},{"line_number":15,"context_line":""},{"line_number":16,"context_line":"Now, TestReconstructorRebuildUTF8 can pass on py3."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":5,"id":"67764bc7_570332e7","line":13,"range":{"start_line":11,"start_character":2,"end_line":13,"end_character":17},"updated":"2025-01-28 17:26:03.000000000","message":"Oh, this should probably get re-worded. Something like\n\n\u003e bufferedhttp needed to parse additional headers earlier, before stdlib tries to establish message framing","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[{"line_number":8,"context_line":""},{"line_number":9,"context_line":"There were two fixes needed:"},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"- bufferedhttp needed to re-assess message framing if/when it needed to"},{"line_number":12,"context_line":"  parse additional headers (in case they included transfer-encoding /"},{"line_number":13,"context_line":"  content-length)"},{"line_number":14,"context_line":"- internal_client needed to encode to bytes before .lower()ing metadata"},{"line_number":15,"context_line":""},{"line_number":16,"context_line":"Now, TestReconstructorRebuildUTF8 can pass on py3."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":5,"id":"8d2615ac_43810de8","line":13,"range":{"start_line":11,"start_character":2,"end_line":13,"end_character":17},"in_reply_to":"67764bc7_570332e7","updated":"2025-02-06 22:12:40.000000000","message":"Done","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9ab1e20ee1183649ac19d04367be14d9564a8afa","unresolved":true,"context_lines":[{"line_number":15,"context_line":""},{"line_number":16,"context_line":"Now, TestReconstructorRebuildUTF8 can pass on py3."},{"line_number":17,"context_line":""},{"line_number":18,"context_line":"Related-Change: https://review.opendev.org/c/openstack/swift/+/662546"},{"line_number":19,"context_line":"Change-Id: I6aa16fda9285c9fc3816da6fbff2615bd14a020c"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":5,"id":"f9c444ea_ea1bdcde","line":18,"updated":"2025-01-29 20:29:26.000000000","message":"this change is called \"py3: Be able to read and write non-ASCII headers\" but it wasn\u0027t the place we introduced the skip-under-py3 for the probe test\n\nwhen did we realize our custom re-parsing breaks the framing issues in some cases?\n\nI guess Change-Id: I4a6877907d14b632a9a477c887913488427b62b7 was what I was looking for?  Or maybe I would have assumed I0f03c211f35a9a49e047a5718a9907b515ca88d7 at least attempted to make some test changes - like \"unskip the tests that prove utf8 headers don\u0027t work under py3\"\n\nThe probe-test skip sort of makes it seem like an \"internal only\" issue with reading py3 utf8 meta keys; is it just not possible to get a chunked-transfer response from swift such that we could write a client facing repro bug in lp?  Maybe a HUGE DLO?","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":false,"context_lines":[{"line_number":15,"context_line":""},{"line_number":16,"context_line":"Now, TestReconstructorRebuildUTF8 can pass on py3."},{"line_number":17,"context_line":""},{"line_number":18,"context_line":"Related-Change: https://review.opendev.org/c/openstack/swift/+/662546"},{"line_number":19,"context_line":"Change-Id: I6aa16fda9285c9fc3816da6fbff2615bd14a020c"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":5,"id":"fcf7cafb_dec69379","line":18,"in_reply_to":"f9c444ea_ea1bdcde","updated":"2025-01-30 22:04:46.000000000","message":"it\u0027s definitely NOT \"just\" a internal bug; these headers wreak havoc all the up the chain anywhere this busted py3 http.client is used:\n\nhttps://bugs.launchpad.net/python-swiftclient/+bug/2097034 \"non ascii metadata key dropped in py3\"","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"57195f23c6ec2ea220c5827961294ae0eada0e90","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"1b9efbc3_d1b7e8d2","updated":"2025-01-10 17:07:10.000000000","message":"As Alistair pointed out that rfc2616#section-4.2 defines ``header name`` at https://datatracker.ietf.org/doc/html/rfc2616#section-4.2:\n``field-name     \u003d token``\nand rfc2616#section-2.2 defines what is ``token`` at https://datatracker.ietf.org/doc/html/rfc2616#section-2.2:\n```\nCTL            \u003d \u003cany US-ASCII control character\n                        (octets 0 - 31) and DEL (127)\u003e\nseparators     \u003d \"(\" | \")\" | \"\u003c\" | \"\u003e\" | \"@\"\n                      | \",\" | \";\" | \":\" | \"\\\" | \u003c\"\u003e\n                      | \"/\" | \"[\" | \"]\" | \"?\" | \"\u003d\"\n                      | \"{\" | \"}\" | SP | HT\ntoken          \u003d 1*\u003cany CHAR except CTLs or separators\u003e\n```\n\nso ``header name`` can only uses characters that are within the ASCII range (0–127), not control characters nor separators.\n\nhow about we just delete this PY2 probe test in this patch? if we like, in another patch we can also add another layer of check in ``bufferedhttp`` or some middleware to enforce that only valid ASCII chars exist in the returned header name.","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"4de5ab051d58a889d0d2305a9a1fff6f6361ba3d","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"37341352_aea130f5","updated":"2025-01-10 10:35:49.000000000","message":"I haven\u0027t fully reviewed the change but feel I should vote -1 if only to flag up that the commit message suggests the test is unreliable and I don\u0027t want to merge an unreliable test. I don\u0027t mean to be snarky, perhaps the commit message is stale? Have you seen the test intermittently fail?\n\nBut I\u0027m also not sure I understand WHY these fixes are needed...\n\nAFAICT header names should be a subset of ascii https://datatracker.ietf.org/doc/html/rfc2616#section-4.2 and same in Swift API doc https://docs.openstack.org/api-ref/object-store/#create-or-update-object-metadata. What\u0027s the context for Swift needing to support unicode in header names - is there a reported bug against Swift? is it that we did and so we have to continue? Or is this purely defensive (ignore non-ascii header but continue parsing the message)?\n\nUpdate: I found this bug https://bugs.launchpad.net/swift/+bug/1172202 - but why did we try to fix it rather than push back on using non-rfc non-ascii header names?\n\nIs this patch a continuation of addressing https://bugs.launchpad.net/swift/+bug/1172202?","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"306b321c2e134c7afe0221a4ea9adf8998142beb","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"eda7641d_e0940d18","in_reply_to":"37341352_aea130f5","updated":"2025-01-10 17:13:25.000000000","message":"\u003e Have you seen the test intermittently fail?\n\nI *was* seeing that, but I think it might be something with my environment -- I keep seeing things about being unable to unmount because the device is busy... I\u0027ll clean up the commit message.\n\n---\n\n\u003e AFAICT header names should be a subset of ascii https://datatracker.ietf.org/doc/html/rfc2616#section-4.2\n\n2616 is at best ambiguous -- it points to [822](https://datatracker.ietf.org/doc/html/rfc822#section-3.1) which is pretty clear about requiring printable ASCII for `field-name`s and arbitrary ASCII for `field-body`s, but then explicitly allows `*TEXT` (`\u003cany OCTET except CTLs, but including LWS\u003e`) in its `field-value`s -- which is presumably why [7230 had to note](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4):\n```\n   Historically, HTTP has allowed field content with text in the\n   ISO-8859-1 charset [ISO-8859-1], supporting other charsets only\n   through use of [RFC2047] encoding.  In practice, most HTTP header\n   field values use only a subset of the US-ASCII charset [USASCII].\n   Newly defined header fields SHOULD limit their field values to\n   US-ASCII octets.  A recipient SHOULD treat other octets in field\n   content (obs-text) as opaque data.\n```\n...which crazy enough, *we support*! You can store header values with arbitrary bytes on objects! So...\n\n\u003e is it that we did and so we have to continue?\n\nYes, that\u0027s it exactly. I don\u0027t want to go losing data that someone wrote years ago just because they included some wonky headers with it *that worked at the time*.\n\n\u003e Is this patch a continuation of addressing https://bugs.launchpad.net/swift/+bug/1172202?\n\nGiven that\n\n- it was marked fixed in 1.9.0 (at a time where we still supported py**26** *because there were still new versions of py26 being released* -- **py3** support was barely a gleam in the eye),\n- the bug (and its causes) were specific to the db layer while this is about the object layer (even if it has impact beyond there), and\n- the trouble manifests quite differently,\n\nI\u0027m going to say no, it\u0027s not really related. I suppose you could argue that they\u0027re both symptoms of the larger meta-bug of us being overly tolerant of what we accept with regard to headers...\n\nFWIW, I also found https://bugs.launchpad.net/swift/+bug/1837805 which is *kind of* related?","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"41fbcfed286fcfaa8cf275c38c2af1777803d176","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"edf89eb7_5eeed598","updated":"2025-01-13 06:03:33.000000000","message":"I am running into this flaky failure with my local vasio, about one failure every 10/20 runs.\n\n```\n    def test_sync_expired_object(self):\n        # verify that missing frag can be rebuilt for an expired object\n        delete_after \u003d 2\n        self.proxy_put(extra_headers\u003d{\u0027x-delete-after\u0027: delete_after})\n        self.proxy_get()  # sanity check\n        orig_frag_headers, orig_frag_etags \u003d self._assert_all_nodes_have_frag(\n            extra_headers\u003d{\u0027X-Backend-Replication\u0027: \u0027True\u0027})\n\n        # wait for object to expire\n        timeout \u003d time.time() + delete_after + 1\n        while time.time() \u003c timeout:\n            try:\n\u003e               self.proxy_get()\n\nswift/test/probe/test_reconstructor_rebuild.py:296:\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\nswift/test/probe/test_reconstructor_rebuild.py:96: in proxy_get\n    status, headers, body \u003d self.int_client.get_object(\nswift/swift/common/internal_client.py:745: in get_object\n    resp \u003d self.make_request(\n.....\n\u003e           raise UnexpectedResponse(msg, resp)\nE           swift.common.internal_client.UnexpectedResponse: Unexpected response: 503 Service Unavailable (b\u0027\u003chtml\u003e\u003ch1\u003eService Unavailable\u003c/h1\u003e\u003cp\u003eThe server is currently unavailable. Please try again at a later time.\u003c/p\u003e\u003c/html\u003e\u0027)\n\nswift/swift/common/internal_client.py:258: UnexpectedResponse\n```\n\nand there are tracebacks (from proxy server I guess?):\n\n```\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/auth/v1.0): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/v1/AUTH_test): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/auth/v1.0): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/v1/AUTH_test2): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/info): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/v1/AUTH_test/container-%C3%A8-e4adb279-5d48-4bbb-bd3e-d645cbf20858): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/v1/AUTH_test/container-%C3%A8-e4adb279-5d48-4bbb-bd3e-d645cbf20858/object-%C3%A8-2a3386c9-d7c5-4dcb-af7e-9cd7895cb5c9): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/v1/AUTH_test/container-%C3%A8-e4adb279-5d48-4bbb-bd3e-d645cbf20858/object-%C3%A8-2a3386c9-d7c5-4dcb-af7e-9cd7895cb5c9): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nWARNING  urllib3.connection:connection.py:512 Failed to parse headers (url\u003dhttp://saio:8080/v1/AUTH_test/container-%C3%A8-e4adb279-5d48-4bbb-bd3e-d645cbf20858/object-%C3%A8-2a3386c9-d7c5-4dcb-af7e-9cd7895cb5c9): expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\nTraceback (most recent call last):\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/connection.py\", line 510, in getresponse\n    assert_header_parsing(httplib_response.msg)\n  File \"/usr/local/lib/python3.8/dist-packages/urllib3/util/response.py\", line 56, in assert_header_parsing\n    raise TypeError(f\"expected httplib.Message, got {type(headers)}.\")\nTypeError: expected httplib.Message, got \u003cclass \u0027http.client.HTTPMessage\u0027\u003e.\n```","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"eaff9bc83af13f55f9978998898f5eb1076d8ffe","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"7ad12b24_cfc84c31","updated":"2025-01-13 22:00:10.000000000","message":"Tim is right that swift should be backward compatible to support users who have stored unicode header names, this fix works as expected. only \u0027+1\u0027 since it would be great for BufferedHTTPResponse::begin() function to have some coverage of unit tests.","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"eaff9bc83af13f55f9978998898f5eb1076d8ffe","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"36f40298_cc03885b","in_reply_to":"04c2dc15_7283b8e0","updated":"2025-01-13 22:00:10.000000000","message":"yes, I could reproduce this flaky failure with master branch, so it\u0027s not related to this patch.\n\n```\nset -e\n\nfor i in $(seq 100); do\n    pytest swift/test/probe/test_reconstructor_rebuild.py::TestReconstructorRebuild::test_sync_expired_object\ndone\n```\n\n```\nE           swiftclient.exceptions.ClientException: Object GET failed: http://saio:8080/v1/AUTH_test/container-11103288-d78a-41c8-8c72-6c7d82ce14e1/object-a05922ae-876d-4a33-aef7-e3b3eda72e42 503 Service Unavailable  [first 60 chars of response] b\u0027\u003chtml\u003e\u003ch1\u003eService Unavailable\u003c/h1\u003e\u003cp\u003eThe server is currently\u0027 (txn: tx12d66b1cfaf74bdd9e040-0067855fce)\n```","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"dcfaf0395d8e7db6affa0cef90dc692d24461c09","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"04c2dc15_7283b8e0","in_reply_to":"edf89eb7_5eeed598","updated":"2025-01-13 17:28:56.000000000","message":"The `httplib.Message` warnings are spurious AFAIK -- I\u0027ve seen them for a while (though I don\u0027t remember at what point I *started* seeing them -- probably with some python upgrade?)","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"105c57a60d4418aaa3047564445534b8729c152f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"4dc806a1_df226701","updated":"2025-01-28 17:24:28.000000000","message":"I\u0027m not sure I can see this yet; but zuul seems happy so it\u0027s probably great!","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"8188937c43b54ee0d5c3c984bef7915f2194bcbd","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"0bdfcf3a_2488f9b4","updated":"2025-01-14 16:40:11.000000000","message":"recheck\n\nBandit failure resolved by https://review.opendev.org/c/openstack/swift/+/939184","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"15db50003e08db2235373ac24c9cc0457d9519d8","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"19c00d9f_9c07082b","updated":"2025-01-14 05:15:07.000000000","message":"recheck\nseems random and unrelated failure in openstack-tox-pep8","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"7fd640607d9ec2fc742f9c4fd93a47b40bff58dd","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"34d7d0c5_5c0e31a2","updated":"2025-01-14 20:04:50.000000000","message":"zuul is unblocked now, will wait for @alistairncoles@gmail.com to take another look.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ff6da46c3e3df5f0d9a754576f5a233237aabe68","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"481abc68_77aac911","updated":"2025-01-30 11:17:53.000000000","message":"I like this better than inlining more code, but reflecting on it I still don\u0027t love it, but nor do I like monkey patching. Anyway, -1 because the current setter is flawed as it is (diff to fix linked in comment).","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9ab1e20ee1183649ac19d04367be14d9564a8afa","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"f1e87557_1c9a938f","updated":"2025-01-29 20:29:26.000000000","message":"I will probably carry this if it\u0027s not merged.  If I can clear my plate I\u0027d want to check on the following before +A myself:\n\na) see how the buffered http unittest fails with the fix reverted (mabye write one to demonstrate the framing issue with SSYNC/chunked-transfer requests who drop headers)\nb) try and repro the python-swiftclient issue and write a lp bug for that project\nc) make sure there shouldn\u0027t be an lp bug for ssync-(meta?)data-loss \nd) write a unittest for the internal-client change\ne) try to replace http.client.parse_headers with something reasonable to avoid all the baggage of email.Message and the wasted effort of the \"ok, do that but again correctly this time\" design of smuggling the fix into a property.\n\n... depending on how far someone else might get on that list before I come back to to review maybe I will have the motivation to try and merge as-is.  A Closes-Bug tag pointing to lp demonstrating ssync data loss might be a big motivator:\n\nhttps://github.com/NVIDIA/swift/blob/master/REVIEW_GUIDELINES.rst#maintainable-code-is-obvious","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"1ed6fad4_232be653","updated":"2025-01-30 22:04:46.000000000","message":"Thanks for getting a solution together Tim; please add a Closes-Bug so operators can check for log messages and triage any reconstructors they\u0027ve had stuck on these non-ascii metadata keys for who knows how long:\n\nhttps://bugs.launchpad.net/swift/+bug/2097030 \"EC reconstructor\u0027s rebuild gets stuck on non-ascii metadata keys\"\n\nI now get why our existing \"fix\" for py3 stdlib http.client non-ascii header key parsing doesn\u0027t \"work\" - so we HAVE to find some way to fix py3 stdlib http.client *somehow* because both the proxy AND the reconstructor use it when talking to backend nodes.  I think this change does that and probably wouldn\u0027t be so bad to just merge; and maybe even get on with:\n\nhttps://bugs.launchpad.net/swift/+bug/1192451 \"Enforce US-ASCII headers (optional)\" esp given https://bugs.launchpad.net/python-swiftclient/+bug/2097034 \"non ascii metadata key dropped in py3\"\n\nHowever, I may also be misunderstanding Al\u0027s reservation with this fix as-is *exactly* - I think this works and fixes the bug; I\u0027m not sure if there\u0027s an actual observed failure or just a theoretical incorrectness.  For my mind there\u0027s no \"right\" way to reach into the guts of httplib.client and fix their bugs...\n\nhttps://github.com/python/cpython/issues/81274\n\nand I think it *is* a bug because *breaking http framing silently* is very bad, and because similar \"these weird headers break http parsing\" bugs when the weird servers sent weird characters for WHATEVER REASON in the header *values* did get fixed:\n\nhttps://github.com/python/cpython/issues/68551\nhttps://github.com/python/cpython/issues/66429\n\n... the reason trying to fix these bugs in our code base is going to be awful no matter what we do is for *exactly* this reason - even when we get it \"right\" and *they don\u0027t change anything* - we still get it wrong on the edge case (e.g. content-length comes after bad meta).  Presumably we could try harder to make the grubbing around inside their class a little more robust (which is what I assume Al\u0027s paste attempts) - but I think stepping *outside of the class* and taking over the `httplib.client.parse_headers` function might turn out to be more maintainable (or get away from it altogether, maybe see how https://github.com/MagicStack/httptools feels about non ascii headers? or just do what we want it in-house; the Email cruft in stdlib is nuts - more correctness may be a performance opportunity here?)\n\nIt was important for me to realize - it\u0027s NOT the header parsing that\u0027s wrong; when you fix the framing issue (e.g. just move content-length to the top of the list of headers swob sends to the wsgi server) the probe test WILL \"get ALL the headers in the response\" (also the metadata comes from the local diskfile anyway)\n\n```\nvagrant@saio:~$ for f in $(find /srv/node*/sdb*/object* -name \\*.data); do echo $f; swift-object-info $f | grep -i x-object-meta; done\n/srv/node1/sdb1/objects-1/776/166/c23f441f3926e31aaee70dcd47f28166/1738266550.70093#0#d.data\n  X-Object-Meta-è-4104C8F4-8D73-4989-8007-16A1044F7E77: meta-foo-è-63028ee7-a0b5-4ff2-910b-a098041ec22c\n/srv/node1/sdb5/objects-1/776/166/c23f441f3926e31aaee70dcd47f28166/1738266550.70093#4#d.data\n  X-Object-Meta-è-4104C8F4-8D73-4989-8007-16A1044F7E77: meta-foo-è-63028ee7-a0b5-4ff2-910b-a098041ec22c\n/srv/node2/sdb2/objects-1/776/166/c23f441f3926e31aaee70dcd47f28166/1738266550.70093#3#d.data\n  X-Object-Meta-è-4104C8F4-8D73-4989-8007-16A1044F7E77: meta-foo-è-63028ee7-a0b5-4ff2-910b-a098041ec22c\n/srv/node3/sdb7/objects-1/776/166/c23f441f3926e31aaee70dcd47f28166/1738266550.70093#2#d.data\n  X-Object-Meta-è-4104C8F4-8D73-4989-8007-16A1044F7E77: meta-foo-è-63028ee7-a0b5-4ff2-910b-a098041ec22c\n/srv/node4/sdb4/objects-1/776/166/c23f441f3926e31aaee70dcd47f28166/1738266550.70093#5#d.data\n  X-Object-Meta-è-4104C8F4-8D73-4989-8007-16A1044F7E77: meta-foo-è-63028ee7-a0b5-4ff2-910b-a098041ec22c\n/srv/node4/sdb8/objects-1/776/166/c23f441f3926e31aaee70dcd47f28166/1738266550.70093#1#d.data\n  X-Object-Meta-è-4104C8F4-8D73-4989-8007-16A1044F7E77: meta-foo-è-63028ee7-a0b5-4ff2-910b-a098041ec22c\n```\n\nThe *problem* was just when reading a peer server http.client resp the \"length \u003d None\" bug that gets thrown off by the silent truncation in the initial/broken/upstream header parsing reliably causes a Timeout without ever sending a byte of the rebuilt frag:\n\n```\nobject-reconstructor-6020: Error trying to rebuild /AUTH_test/container-è-03c9cbb1-2068-4389-9a04-ce7860e9626b/object-è-aa0ed3e8-ce1a-474d-a938-bb5e7014daf8 policy#1 frag#2: Timeout (10.0s)\n```\n\nI don\u0027t know if the reconstructors httplib.client resps thought it was supposed to be a chunked body from the peer object-server and the readline never returned or if it was connection: close resp ... but the reconstructor would get a timeout trying to read from it\u0027s peers (because their http.client resp framing was screwed up) and the ssync_reciever would get hung-up on.\n\nAny workaround/fix for the framing issue our header parsing fix works just fine and everything gets unblocked.  I don\u0027t think there was ever any risk of these framing issues causing [meta]data loss/corruption *directly* - but could *indirectly* result in data loss if you left your reconstructors broken like this too long.  Which I\u0027m not sure would have been that easy to detect:\n\nhttps://bugs.launchpad.net/swift/+bug/2097036 \"EC reconstructor reports 100% sync on failure\"\n\nI didn\u0027t get to:\n\nd) write a unittest for the internal-client change\ne) try to replace http.client.parse_headers with something reasonable to avoid all the baggage of email.Message and the wasted effort of the \"ok, do that but again correctly this time\" design of smuggling the fix into a property.","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"a059cd88_b0cec4ca","in_reply_to":"1ed6fad4_232be653","updated":"2025-02-06 22:12:40.000000000","message":"https://github.com/MagicStack/httptools (and therefore node.js/llhttp) do NOT handle non-ascii meta in headers; even in their most lienient settings:\n\nhttps://github.com/MagicStack/httptools/issues/120","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"f6d86fd0001d720feaf16383468c74c6fb081ad8","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"cebb8a4a_c8056530","updated":"2025-01-31 05:41:18.000000000","message":"I also like the new ``headers.setter`` approach better than inlining the upstream python stdlib code, even though it\u0027s a little bit hacky, and smart as well. We have a working solution and don\u0027t need to maintain the inline python code anymore.\n\nAfter some testing, it seems to me that the ``resp.headers.get_all()`` bug which Al reproduced in his new test cases won\u0027t be triggered during normal path, but who knows if ``headers.setter`` will be called during some http exception paths, so that\u0027ll be great if we can the fixe \u0026 test cases.","commit_id":"047cd2a570c65c0b2761cfc329f20db5493a15b8"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1d92b175e3c607b209c0189672d8bff4cfa90ab2","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"2f80577e_0770c9ea","updated":"2025-01-31 21:19:50.000000000","message":"I\u0027m reluctant to merge w/o any unittest coverage of the internal client change\n\nTim points out the internal client change probably also means the reconciler probe tests would break IF they wrote non-ascii-meta-keys\n\nI\u0027ll try to work on those missing tests next week - but other than that completeness issue - I think this change is correct and helpful.","commit_id":"047cd2a570c65c0b2761cfc329f20db5493a15b8"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ab3f7c26771c8ab2c257381eb182bae0f143e9d4","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"465ff038_8355b2e5","updated":"2025-01-31 18:37:55.000000000","message":"LGTM but ideally we\u0027ll add a test for internal client before merging","commit_id":"047cd2a570c65c0b2761cfc329f20db5493a15b8"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9772f65ebb96e9ae0bd6c07370464fac9277d65f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"a6e66655_7629ce99","updated":"2025-02-06 02:04:02.000000000","message":"I feel the mutation of ``hdrs.set_payload(None)`` is fine, since we are hacking into upstream code (breaks good principles already unfortunately, but that\u0027s still better than inlining the stdlib code IMHO) and overriding its original ``headers.setter`` anyway.","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"5c59dbdf44318c9a4e6092e9a8b9a5d8d3659b38","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"a1681a25_834e83ea","updated":"2025-02-05 22:51:33.000000000","message":"Thanks for adding the InternalClient test!  I now realize it\u0027s a significant behavior change to that interface; and I get that calling lower on utf8 bytes that have been decoded as latin1 had to be a bug no matter HOW you define the interface - but I\u0027m not sure how wide reaching the impact of this change you\u0027ve added to get this one test passing will actually be in practice for \"support\" of non-ascii meta keys - you mentioned the reconciler; should we attempt a probe test with non-ascii meta keys?\n\nMaybe we can just say:\n\nInternalClient isn\u0027t going to be any better than the rest of the http ecosystem at dealing with non-ascii meta keys \"correctly\"\n\n... and just be glad *this* test (which apparently was the only probtest that bothered to deal with non-ascii-meta-keys?) PASSES\n\nIs it total theater to consider if clients can actually treat their meta-keys as case sensitive if we use internal client to move an object?  Do they get to \"work around\" case insenstive normalization by keeping all their meta keys as non-ascii?  If python \"correctly\" handles string.lower/title should we convert the header into the string we know it represents before we call lower on it?  If we want to maintain the \"interface\" that \"normalization of a header means lower *but only ascii bytes*\" should we have a helper to replace `.lower()` so we can do it consistently?\n\n940840: sq: what is a lower normalized meta key anyway? | https://review.opendev.org/c/openstack/swift/+/940840\n\nShould we break all that discussion out to a pre-requisite patch separately from the fix to the bufferedhttp\u0027s parse_header fix?  Surely if we\u0027re going to say \"because of https://bugs.launchpad.net/python-swiftclient/+bug/2097034 using python swiftclient in this test is too busted for reading non-ascii meta keys\" we could find a way to make the assertions we want w/o the InternalClient fix?  Maybe just read from the diskfile directly or use ic.make_request w/o the _get_metadata normalization?","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"5343df17_5e82dee9","updated":"2025-02-06 22:12:40.000000000","message":"I don\u0027t think anything in this change is incorrect or makes swift harder to maintain.\n\nI wish it didn\u0027t have the internal-client drive-by\n\n940897: sq: just fix buffered http \u0026 the probe test | https://review.opendev.org/c/openstack/swift/+/940897\n\nIf we don\u0027t like the property hack and the double header parsing we could merge this \"for now\" and try to consider more seriously about consolidating all our weird hacks for fixing headers according to our particular flavor of http into our common.http_protocol module:\n\n940935: just parse_headers how we want | https://review.opendev.org/c/openstack/swift/+/940935","commit_id":"8261712d603084f92f6d460743566580d58192fc"}],"swift/common/bufferedhttp.py":[{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"3b999ad0a4c8aa9202ce84ac317540366054b5a2","unresolved":false,"context_lines":[{"line_number":91,"context_line":"        self.will_close \u003d _UNKNOWN      # conn will close at end of response"},{"line_number":92,"context_line":"        self._readline_buffer \u003d b\u0027\u0027"},{"line_number":93,"context_line":""},{"line_number":94,"context_line":"    if not six.PY2:"},{"line_number":95,"context_line":"        def begin(self):"},{"line_number":96,"context_line":"            HTTPResponse.begin(self)"},{"line_number":97,"context_line":"            header_payload \u003d self.headers.get_payload()"}],"source_content_type":"text/x-python","patch_set":2,"id":"9e7db929_40a5752e","line":94,"updated":"2025-01-10 06:28:19.000000000","message":"okay, those PY2 related code will be removed in the following patch: https://review.opendev.org/c/openstack/swift/+/853697","commit_id":"42ed14f1e130f90d26059fff448add2f2528842a"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"eaff9bc83af13f55f9978998898f5eb1076d8ffe","unresolved":true,"context_lines":[{"line_number":98,"context_line":"            if isinstance(header_payload, list) and len(header_payload) \u003d\u003d 1:"},{"line_number":99,"context_line":"                header_payload \u003d header_payload[0].get_payload()"},{"line_number":100,"context_line":"            if header_payload:"},{"line_number":101,"context_line":"                # This shouldn\u0027t be here. We must\u0027ve bumped up against"},{"line_number":102,"context_line":"                # https://bugs.python.org/issue37093"},{"line_number":103,"context_line":"                for line in header_payload.rstrip(\u0027\\r\\n\u0027).split(\u0027\\n\u0027):"},{"line_number":104,"context_line":"                    if \u0027:\u0027 not in line or line[:1] in \u0027 \\t\u0027:"}],"source_content_type":"text/x-python","patch_set":3,"id":"d5dbf666_b2e007bf","line":101,"updated":"2025-01-13 22:00:10.000000000","message":"that\u0027ll be great to add a warning log here, then user can monitor this situation in their clusters.","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":true,"context_lines":[{"line_number":98,"context_line":"            if isinstance(header_payload, list) and len(header_payload) \u003d\u003d 1:"},{"line_number":99,"context_line":"                header_payload \u003d header_payload[0].get_payload()"},{"line_number":100,"context_line":"            if header_payload:"},{"line_number":101,"context_line":"                # This shouldn\u0027t be here. We must\u0027ve bumped up against"},{"line_number":102,"context_line":"                # https://bugs.python.org/issue37093"},{"line_number":103,"context_line":"                for line in header_payload.rstrip(\u0027\\r\\n\u0027).split(\u0027\\n\u0027):"},{"line_number":104,"context_line":"                    if \u0027:\u0027 not in line or line[:1] in \u0027 \\t\u0027:"}],"source_content_type":"text/x-python","patch_set":3,"id":"066f3211_1d0da3cb","line":101,"in_reply_to":"29048396_e11e1ffc","updated":"2025-01-30 22:04:46.000000000","message":"\"don\u0027t let clients write NEW meta keys against our API spec and RFC recommendations\" was a good idea for a LONG time:\n\nhttps://bugs.launchpad.net/swift/+bug/1192451\n\n... and if we\u0027d have done it then we might have been able to make a different choices about maintaining support for these legacy meta keys now.  Maybe better late than never?  Doesn\u0027t remove the responsibility to keep it working at least internally; but the ramifications of what it means to \"support\" this crazy client behavior outside of our walls is not defensible.  We need to cut off the bleeding ASAP.","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9ab1e20ee1183649ac19d04367be14d9564a8afa","unresolved":true,"context_lines":[{"line_number":98,"context_line":"            if isinstance(header_payload, list) and len(header_payload) \u003d\u003d 1:"},{"line_number":99,"context_line":"                header_payload \u003d header_payload[0].get_payload()"},{"line_number":100,"context_line":"            if header_payload:"},{"line_number":101,"context_line":"                # This shouldn\u0027t be here. We must\u0027ve bumped up against"},{"line_number":102,"context_line":"                # https://bugs.python.org/issue37093"},{"line_number":103,"context_line":"                for line in header_payload.rstrip(\u0027\\r\\n\u0027).split(\u0027\\n\u0027):"},{"line_number":104,"context_line":"                    if \u0027:\u0027 not in line or line[:1] in \u0027 \\t\u0027:"}],"source_content_type":"text/x-python","patch_set":3,"id":"29048396_e11e1ffc","line":101,"in_reply_to":"c6488f97_8f894423","updated":"2025-01-29 20:29:26.000000000","message":"I agree it\u0027s not immediately actionable.  We don\u0027t want to blow up someone\u0027s log index if there\u0027s nothing they can do about it.\n\nI think in a separate change we could introduce an option that allows a deployer to \"opt-out\" of writing new utf8-meta-keys - that would probably be a *really* good idea for our future deployments.  With such a change it would probably be reasonable to emit some kind of signal when such a metadata is rejected (probably just a stat?) in the uft8-meta-key-disabled mode for existing deployments when we encounter these values (probably in the *auditor* more so than the client read path) it would be a good idea to emit a message that points at the actual path/storage-namespace so ops can work with their user to migrate the \"unsupported\" metadata.\n\nIf you want to migrate an existing cluster to utf8-meta-key-disbaled you might run auditor -zbf on all nodes to get a sense of how many warnings you\u0027re going to generate before you decide if you want to turn it on.  You could also utf8-meta-key-enabled False in the proxy to \"stop the bleeding\" and save the audit/warning/cleanup for another day...","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a4f09cd4a8c2a24c2e8ccbe8d8e6ea30c4602c4c","unresolved":true,"context_lines":[{"line_number":98,"context_line":"            if isinstance(header_payload, list) and len(header_payload) \u003d\u003d 1:"},{"line_number":99,"context_line":"                header_payload \u003d header_payload[0].get_payload()"},{"line_number":100,"context_line":"            if header_payload:"},{"line_number":101,"context_line":"                # This shouldn\u0027t be here. We must\u0027ve bumped up against"},{"line_number":102,"context_line":"                # https://bugs.python.org/issue37093"},{"line_number":103,"context_line":"                for line in header_payload.rstrip(\u0027\\r\\n\u0027).split(\u0027\\n\u0027):"},{"line_number":104,"context_line":"                    if \u0027:\u0027 not in line or line[:1] in \u0027 \\t\u0027:"}],"source_content_type":"text/x-python","patch_set":3,"id":"c6488f97_8f894423","line":101,"in_reply_to":"d5dbf666_b2e007bf","updated":"2025-01-13 23:10:14.000000000","message":"IDK -- a warning about client behavior doesn\u0027t really seem actionable. There\u0027s a reason I pushed for https://github.com/openstack/swift/commit/a61d154f a while back.","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"41fbcfed286fcfaa8cf275c38c2af1777803d176","unresolved":true,"context_lines":[{"line_number":109,"context_line":"                    header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":110,"context_line":"                    value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":111,"context_line":"                    self.headers.add_header(header, value)"},{"line_number":112,"context_line":"                # Since we needed to parse more headers, we also need to"},{"line_number":113,"context_line":"                # re-assess our message framing; the rest is all cribbed from"},{"line_number":114,"context_line":"                # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":115,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"d89c6a7f_fd9ba1f1","line":112,"updated":"2025-01-13 06:03:33.000000000","message":"so HTTPResponse.begin() won\u0027t see those headers with utf-8 characters and the normal headers after them, this function has to manually retrieve them and then we would need to re-assess the message framing.\n\neven though \"the rest is all cribbed from the end of cpython\u0027s HTTPResponse.begin\", how we are going to maintain those code? do we need to copy their test cases over to our repo, what happen if cpython\u0027s HTTPResponse.begin() gets a new bug fix?\n\ncan we call cpython\u0027s HTTPResponse.begin() here again in order to re-assess the message framing?","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"7fd640607d9ec2fc742f9c4fd93a47b40bff58dd","unresolved":false,"context_lines":[{"line_number":109,"context_line":"                    header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":110,"context_line":"                    value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":111,"context_line":"                    self.headers.add_header(header, value)"},{"line_number":112,"context_line":"                # Since we needed to parse more headers, we also need to"},{"line_number":113,"context_line":"                # re-assess our message framing; the rest is all cribbed from"},{"line_number":114,"context_line":"                # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":115,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"0174513e_39edb867","line":112,"in_reply_to":"59f263fe_77105390","updated":"2025-01-14 20:04:50.000000000","message":"Done","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"eaff9bc83af13f55f9978998898f5eb1076d8ffe","unresolved":true,"context_lines":[{"line_number":109,"context_line":"                    header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":110,"context_line":"                    value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":111,"context_line":"                    self.headers.add_header(header, value)"},{"line_number":112,"context_line":"                # Since we needed to parse more headers, we also need to"},{"line_number":113,"context_line":"                # re-assess our message framing; the rest is all cribbed from"},{"line_number":114,"context_line":"                # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":115,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"59f263fe_77105390","line":112,"in_reply_to":"d89c6a7f_fd9ba1f1","updated":"2025-01-13 22:00:10.000000000","message":"No, looked into ``HTTPResponse.begin()``, there is no way to call it and reuse it at here.\n\nThat\u0027ll be great if this ``BufferedHTTPResponse::begin()`` function has some coverage of unit tests, and they can be a follow-up, since this change only impacts the case of unicode header name handling.\n\nand when I reverted the new changes in this function, I saw the failure the author is trying to solve with this patch.\n```\nswift/test/probe/test_reconstructor_rebuild.py:190:\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\nswift/test/probe/test_reconstructor_rebuild.py:179: in _test_rebuild_scenario\n    self.assertEqual(self.frag_headers, frag_headers)\n/usr/lib/python3.8/contextlib.py:131: in __exit__\n    self.gen.throw(type, value, traceback)\nswift/test/probe/test_reconstructor_rebuild.py:133: in _annotate_failure_with_scenario\n    self.fail(\nE   AssertionError: Scenario with failed nodes: [\u0027sdb8#0\u0027], non-durable nodes: []\nE    failed with:\nE       Node \u0027sdb8#0\u0027 raised DirectClientException(\"Object server 127.0.0.4:6040 direct GET \u0027/sdb8/142/AUTH_test/container-%C3%A8-d1b32424-9b29-42b4-907c-2aab9f13d717/object-%C3%A8-d9035030-c2be-435d-aa66-1d8d99162299\u0027 gave status 404\")\n```","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"105c57a60d4418aaa3047564445534b8729c152f","unresolved":true,"context_lines":[{"line_number":81,"context_line":"        self._readline_buffer \u003d b\u0027\u0027"},{"line_number":82,"context_line":""},{"line_number":83,"context_line":"    def begin(self):"},{"line_number":84,"context_line":"        HTTPResponse.begin(self)"},{"line_number":85,"context_line":"        header_payload \u003d self.headers.get_payload()"},{"line_number":86,"context_line":"        if isinstance(header_payload, list) and len(header_payload) \u003d\u003d 1:"},{"line_number":87,"context_line":"            header_payload \u003d header_payload[0].get_payload()"}],"source_content_type":"text/x-python","patch_set":4,"id":"ba98917f_a5502068","line":84,"updated":"2025-01-28 17:24:28.000000000","message":"are we still benefiting from this superclass call?  Would it be easier at this point to just inline the whole method with our \"enhancements\" called out with specific `self._swift_fix_abc()` calls interleaved with the master implementation.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9ab1e20ee1183649ac19d04367be14d9564a8afa","unresolved":false,"context_lines":[{"line_number":81,"context_line":"        self._readline_buffer \u003d b\u0027\u0027"},{"line_number":82,"context_line":""},{"line_number":83,"context_line":"    def begin(self):"},{"line_number":84,"context_line":"        HTTPResponse.begin(self)"},{"line_number":85,"context_line":"        header_payload \u003d self.headers.get_payload()"},{"line_number":86,"context_line":"        if isinstance(header_payload, list) and len(header_payload) \u003d\u003d 1:"},{"line_number":87,"context_line":"            header_payload \u003d header_payload[0].get_payload()"}],"source_content_type":"text/x-python","patch_set":4,"id":"f9ac0553_4596aeeb","line":84,"in_reply_to":"ba98917f_a5502068","updated":"2025-01-29 20:29:26.000000000","message":"not relevant anymore with the headers property appraoch.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"7ef9d468237f10e87a9f9c4753e662ac9deda578","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"54864028_d0ca149d","line":100,"updated":"2025-01-28 14:43:43.000000000","message":"The following looks like a faithful reproduction of the superclass code, but maybe we can we avoid duplicating it all by putting the preceding existing fix in a headers property setter? \n See https://paste.openstack.org/show/bnB8xCCTDj8Kls8XyZpP/\n\nTests passed with that diff.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ff6da46c3e3df5f0d9a754576f5a233237aabe68","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"e719e4ec_aed41570","line":100,"in_reply_to":"1407de72_0203d96e","updated":"2025-01-30 11:17:53.000000000","message":"\u003e and the disadvantage is what @jian was asking about \"are we sure upstream ever only calls the setter once?\"\n\nI couldn\u0027t find this comment. But it got me thinking about why it wouldn\u0027t be safe to call the setter more than once, and led me to notice that repeated setting of the *same* Message would cause headers to duplicate because the payload is parsed, headers are added to the existing headers, but the payload is retained.  This diff fixes that https://paste.openstack.org/show/bsaj0mcnCm51ILkZnqbD/\n\nThe tests in that diff should be useful (with some adaptation) whichever direction we go with the solution.\n\n\u003eIt seems like upstream sort of understood that it should be possible to use a different class implementation of header parsing.\n\nAFAICT the _class arg is to provide an alternative to Message, not an alternative parser. _class needs to support set_payload() for unparsed-header content. I guess set_payload() could then do the additional parsing of un-parsed headers. But we don\u0027t have a route to plumb that in other than monkey-patching parse_headers() anyway.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"7f1e8d0599f9289c07bef798491de6d2dc3c3269","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"554a137c_a02bdc9b","line":100,"in_reply_to":"5394b9c2_5d97f2df","updated":"2025-01-31 01:16:28.000000000","message":"\u003eI couldn\u0027t find this comment. But it got me thinking about why it wouldn\u0027t be safe to call the setter more than once, and led me to notice that repeated setting of the same Message would cause headers to duplicate because the payload is parsed, headers are added to the existing headers, but the payload is retained. This diff fixes that https://paste.openstack.org/show/bsaj0mcnCm51ILkZnqbD/\n\nyes, I was thinking about that and also noticed that the payload is retained after function returns. I did some debug logging with the targeted probe test ``TestReconstructorRebuildUTF8::test_rebuild_missing_frags`` from test_reconstructor_rebuild.py, the ``headers.setter`` within ``BufferedHTTPResponse`` is called once for each read response and twice for the close response, but the probe test happily passes.\n\nI reverted the changes in @alistairncoles@gmail.com \u0027s ``paste`` changes, and the unit test ``test_headers_setter_with_message_with_payload`` would fail. I am not sure who is going to use resp.headers.get_all, but that\u0027ll be great to avoid the doubled-up headers. Let\u0027s squash Al\u0027s changes.\n\n```\n        resp.headers \u003d msg\n        self.assertEqual(\u0027b\u0027, resp.headers.get(\u0027\\xc3\u0027))\n\u003e       self.assertEqual([\u0027b\u0027, \u0027bb\u0027], resp.headers.get_all(\u0027\\xc3\u0027))\nE       AssertionError: Lists differ: [\u0027b\u0027, \u0027bb\u0027] !\u003d [\u0027b\u0027, \u0027bb\u0027, \u0027b\u0027, \u0027bb\u0027]\n```","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"eb086c878748cd101fd73e2c951aa616789fe816","unresolved":false,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"463f9bfe_b8c1cf3d","line":100,"in_reply_to":"54864028_d0ca149d","updated":"2025-01-28 17:23:57.000000000","message":"Good call! Way better to have stdlib just work on the right set of headers the first time through.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"105c57a60d4418aaa3047564445534b8729c152f","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"aef51ba1_f28429d9","line":100,"in_reply_to":"54864028_d0ca149d","updated":"2025-01-28 17:24:28.000000000","message":"It would be nice to see \"the fix\" more clearly called out separately from the \"inline chunked-transfer code from upstream\"; and having a new property with \"magic\" behavior is a way to do that.\n\nMy reservation is that it\u0027s obfuscating the \"migration\" towards a BufferedHTTPResponse that doesn\u0027t inherit from HTTPResponse at all - which I think is where this is going eventually somehow.\n\nIn that situation I\u0027m not sure we\u0027d want utf8 header handling in a property as opposed to inline with `begin()` - or maybe even in `get_pyaload()`?","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9ab1e20ee1183649ac19d04367be14d9564a8afa","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"1407de72_0203d96e","line":100,"in_reply_to":"8244200a_d1ec3465","updated":"2025-01-29 20:29:26.000000000","message":"and the disadvantage is what @jian was asking about \"are we sure upstream ever only calls the setter once?\"\n\nDespite a property as a clever abstraction, it\u0027s slightly more opaque about what\u0027s going on upstream.\n\n\nFWIW it looks like we\u0027re ok:\n\n```\n    def begin(self):\n        if self.headers is not None:\n            # we\u0027ve already started reading the response\n            return\n```\n\n^ from `/usr/lib/python3.10/http/client.py` (which comes into `eventlet.green.http.client` as `io.HTTPResponse`)\n\nIt seems like upstream sort of understood that it should be possible to use a different class implementation of header parsing.\n\n```\ndef parse_headers(fp, _class\u003dHTTPMessage):\n    \"\"\"Parses only RFC2822 headers from a file pointer.\n\n    email Parser wants to see strings rather than bytes.\n    But a TextIOWrapper around self.rfile would buffer too many bytes\n    from the stream, bytes which we later need to read as bytes.\n    So we read the correct bytes here, as bytes, for email Parser\n    to parse.\n\n    \"\"\"\n    headers \u003d _read_headers(fp)\n    hstring \u003d b\u0027\u0027.join(headers).decode(\u0027iso-8859-1\u0027)\n    return email.parser.Parser(_class\u003d_class).parsestr(hstring)\n```\n\nbut it never gets called with that kwarg 😭\n\n```\n        self.headers \u003d self.msg \u003d parse_headers(self.fp)\n```\n\n^ right in the middle of begin!  Why wouldn\u0027t you make this a freaking method!?","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"857c5fe60416d1136c4bbcba1ef6069212d02446","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"b3af9bad_1e8434de","line":100,"in_reply_to":"aef51ba1_f28429d9","updated":"2025-01-28 23:28:16.000000000","message":"so there is a migration towards ``a BufferedHTTPResponse that doesn\u0027t inherit from HTTPResponse``, I wonder why?","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"c7d38806b45a652b389d482fb011ce64fe86ab85","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"8244200a_d1ec3465","line":100,"in_reply_to":"b3af9bad_1e8434de","updated":"2025-01-29 01:08:57.000000000","message":"With Alistair\u0027s idea, it actually is a step *away* from us having a `BufferedHTTPResponse` divorced from stdlib. We might still want to do that, but the previous patchset is the closest I\u0027ve seen to anything like a plan for that, and it still didn\u0027t take that leap.\n\nSo the thought is, if we\u0027re still leaning on stdlib anyway, let\u0027s minimize how much we have to duplicate from upstream. By just implementing the `headers` property, we no longer need to touch `begin` or worry about keeping up with whatever changes stdlib may make to it.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"fc9f54b66e6c1b538a3a6ed36add8ff210d943a0","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"a124808a_3c85dcad","line":100,"in_reply_to":"e719e4ec_aed41570","updated":"2025-01-30 21:22:34.000000000","message":"\u003e repeated setting of the *same* Message would cause headers to duplicate because the payload is parsed, headers are added to the existing headers, but the payload is retained.\n\nWhy go the use-a-new-`Message` route instead of just calling `hdrs.set_payload(None)` after parsing?","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":98,"context_line":"                value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":99,"context_line":"                self.headers.add_header(header, value)"},{"line_number":100,"context_line":"            # Since we needed to parse more headers, we also need to"},{"line_number":101,"context_line":"            # re-assess our message framing; the rest is all cribbed from"},{"line_number":102,"context_line":"            # the end of cpython\u0027s HTTPResponse.begin"},{"line_number":103,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"5394b9c2_5d97f2df","line":100,"in_reply_to":"e719e4ec_aed41570","updated":"2025-01-30 22:04:46.000000000","message":"thanks for pointing out that I misunderstood that the \"injectable\" HTTPMessage does NOT acctually control the parsing of the http headers - that\u0027s still always done by the email parser\n\n    email.feedparser.headerRE \u003d re.compile(r\u0027^(From |[\\041-\\071\\073-\\176]*:|[\\t ])\u0027)\n\n... so if we want to \"just read the fp, parse the bytes, and return a compatible msg obj\" we\u0027re probably better off replacing http.client.parse_headers wholesale.  It\u0027s only three lines and we can still use `_read_headers`.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9ab1e20ee1183649ac19d04367be14d9564a8afa","unresolved":true,"context_lines":[{"line_number":41,"context_line":"# Apparently http.server uses this to decide when/whether to send a 431."},{"line_number":42,"context_line":"# Give it some slack, so the app is more likely to get the chance to reject"},{"line_number":43,"context_line":"# with a 400 instead."},{"line_number":44,"context_line":"http.client._MAXHEADERS \u003d constraints.MAX_HEADER_COUNT * 1.6"},{"line_number":45,"context_line":"green_http_client._MAXHEADERS \u003d constraints.MAX_HEADER_COUNT * 1.6"},{"line_number":46,"context_line":""},{"line_number":47,"context_line":""}],"source_content_type":"text/x-python","patch_set":5,"id":"27a2dda9_f4c9aa38","line":44,"updated":"2025-01-29 20:29:26.000000000","message":"since we\u0027re into the monkey patch anyway I wonder why not just replace the `http.client.parse_headers` with a better implementation (instead of doing it wrong and then fixing it)","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":true,"context_lines":[{"line_number":41,"context_line":"# Apparently http.server uses this to decide when/whether to send a 431."},{"line_number":42,"context_line":"# Give it some slack, so the app is more likely to get the chance to reject"},{"line_number":43,"context_line":"# with a 400 instead."},{"line_number":44,"context_line":"http.client._MAXHEADERS \u003d constraints.MAX_HEADER_COUNT * 1.6"},{"line_number":45,"context_line":"green_http_client._MAXHEADERS \u003d constraints.MAX_HEADER_COUNT * 1.6"},{"line_number":46,"context_line":""},{"line_number":47,"context_line":""}],"source_content_type":"text/x-python","patch_set":5,"id":"218e959d_85c2272b","line":44,"in_reply_to":"27a2dda9_f4c9aa38","updated":"2025-02-06 22:12:40.000000000","message":"why not?  because green_http_client doesn\u0027t even *use* http.client.parse_headers anymore - *we* do in getexpect\n\nhttps://review.opendev.org/c/openstack/swift/+/940935/1/swift/common/bufferedhttp.py#104\n\neventlet already inlined all that stuff, so we could patch green_http_client.parse_headers and in many ways I think that may be a better approach.","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ff6da46c3e3df5f0d9a754576f5a233237aabe68","unresolved":true,"context_lines":[{"line_number":84,"context_line":"    def headers(self):"},{"line_number":85,"context_line":"        return self._headers"},{"line_number":86,"context_line":""},{"line_number":87,"context_line":"    @headers.setter"},{"line_number":88,"context_line":"    def headers(self, hdrs):"},{"line_number":89,"context_line":"        try:"},{"line_number":90,"context_line":"            header_payload \u003d hdrs.get_payload()"}],"source_content_type":"text/x-python","patch_set":5,"id":"8046e066_e3b8dcaa","line":87,"updated":"2025-01-30 11:17:53.000000000","message":"hmmm, what about ``self.msg``? It seems to be an alias for ``self.headers``, so should it also be set as a side-effect of this setter? That seems pretty yuk, but so does it having a different value. Or we do we  define ``msg`\u0027 as a property as well?","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"fc9f54b66e6c1b538a3a6ed36add8ff210d943a0","unresolved":true,"context_lines":[{"line_number":84,"context_line":"    def headers(self):"},{"line_number":85,"context_line":"        return self._headers"},{"line_number":86,"context_line":""},{"line_number":87,"context_line":"    @headers.setter"},{"line_number":88,"context_line":"    def headers(self, hdrs):"},{"line_number":89,"context_line":"        try:"},{"line_number":90,"context_line":"            header_payload \u003d hdrs.get_payload()"}],"source_content_type":"text/x-python","patch_set":5,"id":"d4636ffb_88d61330","line":87,"in_reply_to":"8046e066_e3b8dcaa","updated":"2025-01-30 21:22:34.000000000","message":"As the patch stands now, `self._headers is self.msg`, right? At least, when run through https://github.com/python/cpython/blob/main/Lib/http/client.py#L350\n\nSo it seems like we just need to fix the double-parsing.","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ab3f7c26771c8ab2c257381eb182bae0f143e9d4","unresolved":false,"context_lines":[{"line_number":84,"context_line":"    def headers(self):"},{"line_number":85,"context_line":"        return self._headers"},{"line_number":86,"context_line":""},{"line_number":87,"context_line":"    @headers.setter"},{"line_number":88,"context_line":"    def headers(self, hdrs):"},{"line_number":89,"context_line":"        try:"},{"line_number":90,"context_line":"            header_payload \u003d hdrs.get_payload()"}],"source_content_type":"text/x-python","patch_set":5,"id":"d4986c95_b2a25ea8","line":87,"in_reply_to":"d4636ffb_88d61330","updated":"2025-01-31 18:37:55.000000000","message":"Done","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"857c5fe60416d1136c4bbcba1ef6069212d02446","unresolved":true,"context_lines":[{"line_number":85,"context_line":"        return self._headers"},{"line_number":86,"context_line":""},{"line_number":87,"context_line":"    @headers.setter"},{"line_number":88,"context_line":"    def headers(self, hdrs):"},{"line_number":89,"context_line":"        try:"},{"line_number":90,"context_line":"            header_payload \u003d hdrs.get_payload()"},{"line_number":91,"context_line":"        except AttributeError:"}],"source_content_type":"text/x-python","patch_set":5,"id":"11c25492_865f0484","line":88,"updated":"2025-01-28 23:28:16.000000000","message":"I see, the class will re‐runs the payload parsing any time headers is reassigned, and then the message framing gets re-assessed rather than only during the initial begin() call","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9772f65ebb96e9ae0bd6c07370464fac9277d65f","unresolved":false,"context_lines":[{"line_number":85,"context_line":"        return self._headers"},{"line_number":86,"context_line":""},{"line_number":87,"context_line":"    @headers.setter"},{"line_number":88,"context_line":"    def headers(self, hdrs):"},{"line_number":89,"context_line":"        try:"},{"line_number":90,"context_line":"            header_payload \u003d hdrs.get_payload()"},{"line_number":91,"context_line":"        except AttributeError:"}],"source_content_type":"text/x-python","patch_set":5,"id":"bfcc8dfe_2e4a1e3a","line":88,"in_reply_to":"11c25492_865f0484","updated":"2025-02-06 02:04:02.000000000","message":"Acknowledged","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ff6da46c3e3df5f0d9a754576f5a233237aabe68","unresolved":true,"context_lines":[{"line_number":104,"context_line":"                        break"},{"line_number":105,"context_line":"                    header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":106,"context_line":"                    value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":107,"context_line":"                    hdrs.add_header(header, value)"},{"line_number":108,"context_line":"        self._headers \u003d hdrs"},{"line_number":109,"context_line":""},{"line_number":110,"context_line":"    def expect_response(self):"}],"source_content_type":"text/x-python","patch_set":5,"id":"9b51d661_a0fdb39e","line":107,"range":{"start_line":107,"start_character":20,"end_line":107,"end_character":50},"updated":"2025-01-30 11:17:53.000000000","message":"email.message.Message supports multiple headers having the same name. So each time the setter is called with the same Message the headers in the payload will get accumulated.\n\nSee tests and fix here https://paste.openstack.org/show/bsaj0mcnCm51ILkZnqbD/","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ab3f7c26771c8ab2c257381eb182bae0f143e9d4","unresolved":false,"context_lines":[{"line_number":104,"context_line":"                        break"},{"line_number":105,"context_line":"                    header, value \u003d line.split(\u0027:\u0027, 1)"},{"line_number":106,"context_line":"                    value \u003d value.strip(\u0027 \\t\\n\\r\u0027)"},{"line_number":107,"context_line":"                    hdrs.add_header(header, value)"},{"line_number":108,"context_line":"        self._headers \u003d hdrs"},{"line_number":109,"context_line":""},{"line_number":110,"context_line":"    def expect_response(self):"}],"source_content_type":"text/x-python","patch_set":5,"id":"529c0ee2_f501d1ac","line":107,"range":{"start_line":107,"start_character":20,"end_line":107,"end_character":50},"in_reply_to":"9b51d661_a0fdb39e","updated":"2025-01-31 18:37:55.000000000","message":"Done","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"758d1994ae8f49304f6d9f3394022d224378c89e","unresolved":true,"context_lines":[{"line_number":108,"context_line":"                # Clear the payload now that all headers are present."},{"line_number":109,"context_line":"                # Otherwise, we may double-up the headers parsed here"},{"line_number":110,"context_line":"                # if/when repeatedly setting the headers property."},{"line_number":111,"context_line":"                hdrs.set_payload(None)"},{"line_number":112,"context_line":"        self._headers \u003d hdrs"},{"line_number":113,"context_line":""},{"line_number":114,"context_line":"    def expect_response(self):"}],"source_content_type":"text/x-python","patch_set":6,"id":"fbf527ab_f096c481","line":111,"updated":"2025-01-31 15:29:45.000000000","message":"I dislike the idea of mutating the object that was passed in. It\u0027s reasonable in \u0027private\u0027 code but we can\u0027t really avoid this appearing to be a public interface. That\u0027s why I suggested cutting a new instance of Message.","commit_id":"047cd2a570c65c0b2761cfc329f20db5493a15b8"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"ab3f7c26771c8ab2c257381eb182bae0f143e9d4","unresolved":true,"context_lines":[{"line_number":108,"context_line":"                # Clear the payload now that all headers are present."},{"line_number":109,"context_line":"                # Otherwise, we may double-up the headers parsed here"},{"line_number":110,"context_line":"                # if/when repeatedly setting the headers property."},{"line_number":111,"context_line":"                hdrs.set_payload(None)"},{"line_number":112,"context_line":"        self._headers \u003d hdrs"},{"line_number":113,"context_line":""},{"line_number":114,"context_line":"    def expect_response(self):"}],"source_content_type":"text/x-python","patch_set":6,"id":"ffcd86e2_926ea276","line":111,"in_reply_to":"5bc4ebde_d14ee69c","updated":"2025-01-31 18:37:55.000000000","message":"yeah. Once we\u0027re overriding code we don\u0027t maintain it\u0027s hard. My dislike is in \u0027principle\u0027 not \u0027practice\u0027 i.e. I don\u0027t think there\u0027s any immediate regression either way. And I also realise that if a caller passes in a mutable to any function then they have to beware mutations. It\u0027s just that if I think about it I\u0027d try not to be *that* function.\n\nPerhaps we could slap a docstring on the setter like\n\n\n```\n\"\"\"\n:param hdrs: an instance email.message.Message. This setter will attempt\n    to further parse any headers left in the Message payload and in doing\n    so remove the payload and add to the headers of the instance.\n\"\"\"\n```","commit_id":"047cd2a570c65c0b2761cfc329f20db5493a15b8"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":true,"context_lines":[{"line_number":108,"context_line":"                # Clear the payload now that all headers are present."},{"line_number":109,"context_line":"                # Otherwise, we may double-up the headers parsed here"},{"line_number":110,"context_line":"                # if/when repeatedly setting the headers property."},{"line_number":111,"context_line":"                hdrs.set_payload(None)"},{"line_number":112,"context_line":"        self._headers \u003d hdrs"},{"line_number":113,"context_line":""},{"line_number":114,"context_line":"    def expect_response(self):"}],"source_content_type":"text/x-python","patch_set":6,"id":"7f9841a7_342f9f6c","line":111,"in_reply_to":"d89b1bde_1cea7416","updated":"2025-02-06 22:12:40.000000000","message":"I think there\u0027s some merit in this approach:\n\n940935: just parse_headers how we want | https://review.opendev.org/c/openstack/swift/+/940935\n\nWe have to pull at less shoe-strings to redo work in order to get the behavior we want - and there\u0027s a single place very close the wire that we get to do what we want correctly the first time and make everyone else fall in line.","commit_id":"047cd2a570c65c0b2761cfc329f20db5493a15b8"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"9333f47f6ff12e51cffca473dce0dcd721e93ed7","unresolved":true,"context_lines":[{"line_number":108,"context_line":"                # Clear the payload now that all headers are present."},{"line_number":109,"context_line":"                # Otherwise, we may double-up the headers parsed here"},{"line_number":110,"context_line":"                # if/when repeatedly setting the headers property."},{"line_number":111,"context_line":"                hdrs.set_payload(None)"},{"line_number":112,"context_line":"        self._headers \u003d hdrs"},{"line_number":113,"context_line":""},{"line_number":114,"context_line":"    def expect_response(self):"}],"source_content_type":"text/x-python","patch_set":6,"id":"5bc4ebde_d14ee69c","line":111,"in_reply_to":"fbf527ab_f096c481","updated":"2025-01-31 16:43:09.000000000","message":"I get that, but fixing the problem at its root seems best to me. There\u0027s nothing stopping stdlib _today_ changing\n```\nself.headers \u003d self.msg \u003d parse_headers(self.fp)\n...\ntr_enc \u003d self.headers.get(\"transfer-encoding\")\n...\n```\nto be something like\n```\nhdrs \u003d parse_headers(self.fp)\nself.headers \u003d self.msg \u003d hdrs\n...\ntr_enc \u003d hdrs.get(\"transfer-encoding\")\n...\n```\nand we\u0027re right back where we started -- unless we mutate.","commit_id":"047cd2a570c65c0b2761cfc329f20db5493a15b8"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1d92b175e3c607b209c0189672d8bff4cfa90ab2","unresolved":true,"context_lines":[{"line_number":108,"context_line":"                # Clear the payload now that all headers are present."},{"line_number":109,"context_line":"                # Otherwise, we may double-up the headers parsed here"},{"line_number":110,"context_line":"                # if/when repeatedly setting the headers property."},{"line_number":111,"context_line":"                hdrs.set_payload(None)"},{"line_number":112,"context_line":"        self._headers \u003d hdrs"},{"line_number":113,"context_line":""},{"line_number":114,"context_line":"    def expect_response(self):"}],"source_content_type":"text/x-python","patch_set":6,"id":"d89b1bde_1cea7416","line":111,"in_reply_to":"fbf527ab_f096c481","updated":"2025-01-31 21:19:50.000000000","message":"that makes sense, I dislike the idea of using a property - I would like to try just monkey patching `httplib.client.parse_headers` but working code wins; AFAIK it\u0027s better to ship this than not?","commit_id":"047cd2a570c65c0b2761cfc329f20db5493a15b8"}],"swift/common/internal_client.py":[{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"41fbcfed286fcfaa8cf275c38c2af1777803d176","unresolved":true,"context_lines":[{"line_number":298,"context_line":"        for k, v in resp.headers.items():"},{"line_number":299,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":300,"context_line":"                lkey \u003d k[len(metadata_prefix):].encode("},{"line_number":301,"context_line":"                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)"},{"line_number":302,"context_line":"                metadata[lkey] \u003d v"},{"line_number":303,"context_line":"        return metadata"},{"line_number":304,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"b8fe9711_31f0bd07","line":301,"updated":"2025-01-13 06:03:33.000000000","message":"is this necessary, are those header keys already ``latin1`` encoded when returned from swift backend?","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"eaff9bc83af13f55f9978998898f5eb1076d8ffe","unresolved":false,"context_lines":[{"line_number":298,"context_line":"        for k, v in resp.headers.items():"},{"line_number":299,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":300,"context_line":"                lkey \u003d k[len(metadata_prefix):].encode("},{"line_number":301,"context_line":"                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)"},{"line_number":302,"context_line":"                metadata[lkey] \u003d v"},{"line_number":303,"context_line":"        return metadata"},{"line_number":304,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"9d5d7483_9740dbee","line":301,"in_reply_to":"05ef0b73_ab77f99a","updated":"2025-01-13 22:00:10.000000000","message":"Thanks for this good example, I translated it into this below code, helped me to understand why this change is needed.\n\n```\n# Define old function\ndef old_get_metadata_key(key, metadata_prefix):\n    stripped_key \u003d key[len(metadata_prefix):]\n    return stripped_key.lower()\n\n# Define new function\ndef new_get_metadata_key(key, metadata_prefix):\n    stripped_key \u003d key[len(metadata_prefix):]\n    lkey \u003d stripped_key.encode(\u0027latin1\u0027).lower().decode(\u0027latin1\u0027)\n    return lkey\n\n# The header \"X-Object-Meta-My-Méta\" in UTF-8 encoding\nutf8_encoded_header_key \u003d b\u0027X-Object-Meta-My-M\\xc3\\xa9ta\u0027\nprint(\"utf8_encoded_header_key:\", utf8_encoded_header_key)\n# When decoded as latin1, this becomes \"X-Object-Meta-My-MÃ©ta\"\nlatin1_decoded_key \u003d utf8_encoded_header_key.decode(\u0027latin1\u0027)\nprint(\"latin1_decoded_key:\", latin1_decoded_key)\n\nmetadata_prefix \u003d \"X-Object-Meta-My-\"\n\ntry:\n    old_result \u003d old_get_metadata_key(latin1_decoded_key, metadata_prefix)\n    print(\"Old function result:\", old_result)\n    # Show how it can\u0027t be interpreted as \"méta\"\n    interpreted_as_utf8 \u003d old_result.encode(\u0027latin1\u0027).decode(\u0027utf-8\u0027)\n    print(\"Old function result interpreted as UTF-8:\", interpreted_as_utf8)\nexcept Exception as e:\n    print(\"Old function raised an exception:\", e)\n\ntry:\n    new_result \u003d new_get_metadata_key(latin1_decoded_key, metadata_prefix)\n    print(\"New function result:\", new_result)\n    # Show how it can be interpreted as \"méta\"\n    interpreted_as_utf8 \u003d new_result.encode(\u0027latin1\u0027).decode(\u0027utf-8\u0027)\n    print(\"New function result interpreted as UTF-8:\", interpreted_as_utf8)\nexcept Exception as e:\n    print(\"New function raised an exception:\", e)\n```\n\nthe output:\n\n```\n% python3.9 test_lower.py\nutf8_encoded_header_key: b\u0027X-Object-Meta-My-M\\xc3\\xa9ta\u0027\nlatin1_decoded_key: X-Object-Meta-My-MÃ©ta\nOld function result: mã©ta\nOld function raised an exception: \u0027utf-8\u0027 codec can\u0027t decode bytes in position 1-2: invalid continuation byte\nNew function result: mÃ©ta\nNew function result interpreted as UTF-8: méta\n```\n\nand when I reverted this change, I saw the expected test case failure:\n```\n\u003e           self.assertIn(wsgi_key, headers)\nE           AssertionError: \u0027x-object-meta-Ã¨-0ccf906d-69b0-4676-8de6-6a6559fece18\u0027 not found in {\u0027content-type\u0027: \u0027application/octet-stream\u0027, \u0027x-object-meta-ã¨-0ccf906d-69b0-4676-8de6-6a6559fece18\u0027: \u0027meta-bar-Ã¨-ac4b5daa-e476-4085-ad54-d388cd909063\u0027, \u0027etag\u0027: \u0027c4bb23698f14f1c47ff86651d85506ac\u0027, \u0027last-modified\u0027: \u0027Mon, 13 Jan 2025 21:30:05 GMT\u0027, \u0027x-timestamp\u0027: \u00271736803804.74877\u0027, \u0027x-backend-timestamp\u0027: \u00271736803804.74877\u0027, \u0027x-backend-data-timestamp\u0027: \u00271736803804.68853\u0027, \u0027x-backend-durable-timestamp\u0027: \u00271736803804.68853\u0027, \u0027x-backend-fragments\u0027: \u0027{\"1736803804.68853\": [0]}\u0027, \u0027accept-ranges\u0027: \u0027bytes\u0027, \u0027content-length\u0027: \u00273670016\u0027, \u0027x-trans-id\u0027: \u0027tx7f6d82c0f9c54ca9a8044-00678585de\u0027, \u0027x-openstack-request-id\u0027: \u0027tx7f6d82c0f9c54ca9a8044-00678585de\u0027}\n\nswift/test/probe/test_reconstructor_rebuild.py:444: AssertionError\n```","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"105c57a60d4418aaa3047564445534b8729c152f","unresolved":true,"context_lines":[{"line_number":298,"context_line":"        for k, v in resp.headers.items():"},{"line_number":299,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":300,"context_line":"                lkey \u003d k[len(metadata_prefix):].encode("},{"line_number":301,"context_line":"                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)"},{"line_number":302,"context_line":"                metadata[lkey] \u003d v"},{"line_number":303,"context_line":"        return metadata"},{"line_number":304,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"6f97f5f9_ea9113f8","line":301,"in_reply_to":"05ef0b73_ab77f99a","updated":"2025-01-28 17:24:28.000000000","message":"this would be great to explore in a unittest; I don\u0027t see any new tests for the InternalClient change.  Can we translate test_lower.py into a unit or probetest?","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[{"line_number":298,"context_line":"        for k, v in resp.headers.items():"},{"line_number":299,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":300,"context_line":"                lkey \u003d k[len(metadata_prefix):].encode("},{"line_number":301,"context_line":"                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)"},{"line_number":302,"context_line":"                metadata[lkey] \u003d v"},{"line_number":303,"context_line":"        return metadata"},{"line_number":304,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"07abea86_91e6f036","line":301,"in_reply_to":"3d92bc70_193ce7a3","updated":"2025-02-06 22:12:40.000000000","message":"it was the same for *that* character; but there\u0027s LOTS of characters in the unicode namespace; there was a significant *semantic* difference between my change and Tim\u0027s it wasn\u0027t just a style question - I didn\u0027t understand how/why we\u0027d define one or the other as correct.  Turns out there is a defintion for correct - it was buried in swob:\n\nhttps://review.opendev.org/c/openstack/swift/+/940840/2/swift/common/swob.py#210","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"5c59dbdf44318c9a4e6092e9a8b9a5d8d3659b38","unresolved":true,"context_lines":[{"line_number":298,"context_line":"        for k, v in resp.headers.items():"},{"line_number":299,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":300,"context_line":"                lkey \u003d k[len(metadata_prefix):].encode("},{"line_number":301,"context_line":"                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)"},{"line_number":302,"context_line":"                metadata[lkey] \u003d v"},{"line_number":303,"context_line":"        return metadata"},{"line_number":304,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"f4086235_17c68cb6","line":301,"in_reply_to":"6f97f5f9_ea9113f8","updated":"2025-02-05 22:51:33.000000000","message":"thanks for the test; it helped a lot.\n\nHow do you know this code shouldn\u0027t be:\n\n```\ndiff --git a/swift/common/internal_client.py b/swift/common/internal_client.py\nindex 9010fd010..70aa1c4c7 100644\n--- a/swift/common/internal_client.py\n+++ b/swift/common/internal_client.py\n@@ -288,10 +288,9 @@ class InternalClient(object):\n         metadata_prefix \u003d metadata_prefix.lower()\n         metadata \u003d {}\n         for k, v in resp.headers.items():\n-            if k.lower().startswith(metadata_prefix):\n-                lkey \u003d k[len(metadata_prefix):].encode(\n-                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)\n-                metadata[lkey] \u003d v\n+            lkey \u003d k.encode(\u0027latin1\u0027).decode(\u0027utf8\u0027).lower().encode(\u0027utf8\u0027).decode(\u0027latin1\u0027)\n+            if lkey.startswith(metadata_prefix):\n+                metadata[lkey[len(metadata_prefix):]] \u003d v\n         return metadata\n \n     def _iter_items(\n```","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"dcfaf0395d8e7db6affa0cef90dc692d24461c09","unresolved":true,"context_lines":[{"line_number":298,"context_line":"        for k, v in resp.headers.items():"},{"line_number":299,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":300,"context_line":"                lkey \u003d k[len(metadata_prefix):].encode("},{"line_number":301,"context_line":"                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)"},{"line_number":302,"context_line":"                metadata[lkey] \u003d v"},{"line_number":303,"context_line":"        return metadata"},{"line_number":304,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"05ef0b73_ab77f99a","line":301,"in_reply_to":"b8fe9711_31f0bd07","updated":"2025-01-13 17:28:56.000000000","message":"Previously we\u0027d lowercase characters outside the A-Z range, such as taking Ã to ã -- which causes a UTF-8 encoded header to no longer be decodable.\n\nSo if I want a header like `X-Object-Meta-My-Méta` (`b\u0027X-Object-Meta-My-M\\xc3\\xa9ta\u0027`) it gets into Python-land looking like `X-Object-Meta-My-MÃ©ta`. Naively `lower()`ing it, we get `x-object-meta-my-mã©ta`, which sure enough can be encoded with Latin-1 (`b\u0027x-object-meta-my-m\\xe3\\xa9ta\u0027`), but *that isn\u0027t what we wanted stored* and in fact can\u0027t be decoded as UTF-8.","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9772f65ebb96e9ae0bd6c07370464fac9277d65f","unresolved":true,"context_lines":[{"line_number":298,"context_line":"        for k, v in resp.headers.items():"},{"line_number":299,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":300,"context_line":"                lkey \u003d k[len(metadata_prefix):].encode("},{"line_number":301,"context_line":"                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)"},{"line_number":302,"context_line":"                metadata[lkey] \u003d v"},{"line_number":303,"context_line":"        return metadata"},{"line_number":304,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"3d92bc70_193ce7a3","line":301,"in_reply_to":"f4086235_17c68cb6","updated":"2025-02-06 02:04:02.000000000","message":"your change also works, output as same as current code in patch.\n```\nNew function result interpreted as UTF-8 (corrected): méta\n```","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"5c59dbdf44318c9a4e6092e9a8b9a5d8d3659b38","unresolved":true,"context_lines":[{"line_number":289,"context_line":"        metadata \u003d {}"},{"line_number":290,"context_line":"        for k, v in resp.headers.items():"},{"line_number":291,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":292,"context_line":"                metadata[k[len(metadata_prefix):].lower()] \u003d v"},{"line_number":293,"context_line":"        return metadata"},{"line_number":294,"context_line":""},{"line_number":295,"context_line":"    def _iter_items("}],"source_content_type":"text/x-python","patch_set":8,"id":"46d72441_8cc6747d","side":"PARENT","line":292,"updated":"2025-02-05 22:51:33.000000000","message":"kind of silly that we `lower()` twice","commit_id":"128124cdd8ca09136d4988affd1bb8c5c1361fc1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[{"line_number":289,"context_line":"        metadata \u003d {}"},{"line_number":290,"context_line":"        for k, v in resp.headers.items():"},{"line_number":291,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":292,"context_line":"                metadata[k[len(metadata_prefix):].lower()] \u003d v"},{"line_number":293,"context_line":"        return metadata"},{"line_number":294,"context_line":""},{"line_number":295,"context_line":"    def _iter_items("}],"source_content_type":"text/x-python","patch_set":8,"id":"10b5e702_7c07cca5","side":"PARENT","line":292,"in_reply_to":"46d72441_8cc6747d","updated":"2025-02-06 22:12:40.000000000","message":"Done","commit_id":"128124cdd8ca09136d4988affd1bb8c5c1361fc1"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9772f65ebb96e9ae0bd6c07370464fac9277d65f","unresolved":true,"context_lines":[{"line_number":289,"context_line":"        metadata \u003d {}"},{"line_number":290,"context_line":"        for k, v in resp.headers.items():"},{"line_number":291,"context_line":"            if k.lower().startswith(metadata_prefix):"},{"line_number":292,"context_line":"                lkey \u003d k[len(metadata_prefix):].encode("},{"line_number":293,"context_line":"                    \u0027latin1\u0027).lower().decode(\u0027latin1\u0027)"},{"line_number":294,"context_line":"                metadata[lkey] \u003d v"},{"line_number":295,"context_line":"        return metadata"}],"source_content_type":"text/x-python","patch_set":8,"id":"e402f17e_9dddc48b","line":292,"updated":"2025-02-06 02:04:02.000000000","message":"worth to add comments like below?\n```\n# header names are wsgi strings, special encoding and decoding are needed to handle header \n# names correctly which containers unicode characters...\n#\n# NOTE: set_metadata doesn\u0027t support this.\n```","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"}],"test/probe/test_reconstructor_rebuild.py":[{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"3b999ad0a4c8aa9202ce84ac317540366054b5a2","unresolved":true,"context_lines":[{"line_number":526,"context_line":"            self.assertEqual([], lines)"},{"line_number":527,"context_line":""},{"line_number":528,"context_line":""},{"line_number":529,"context_line":"if six.PY2:"},{"line_number":530,"context_line":"    # The non-ASCII chars in metadata cause test hangs in"},{"line_number":531,"context_line":"    # _assert_all_nodes_have_frag because of https://bugs.python.org/issue37093"},{"line_number":532,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"01cc61c3_2bbd530d","side":"PARENT","line":529,"updated":"2025-01-10 06:28:19.000000000","message":"IIUC, the starting point of this patch is to convert this probe test from PY2-only to PY3, and the reason why it didn\u0027t support PY3 is due to this python3 bug: https://github.com/python/cpython/issues/81274\n\nBut I am thinking, if this probe test was only being ran under PY2, and there was no other test case which swift needs to use utf-8 encoded character in the header name, can we just delete this probe test? also I wonder if swift would run into this in the production settings.","commit_id":"94d3a5dee8122e14be0b00694d65c448ce47ea31"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"306b321c2e134c7afe0221a4ea9adf8998142beb","unresolved":true,"context_lines":[{"line_number":526,"context_line":"            self.assertEqual([], lines)"},{"line_number":527,"context_line":""},{"line_number":528,"context_line":""},{"line_number":529,"context_line":"if six.PY2:"},{"line_number":530,"context_line":"    # The non-ASCII chars in metadata cause test hangs in"},{"line_number":531,"context_line":"    # _assert_all_nodes_have_frag because of https://bugs.python.org/issue37093"},{"line_number":532,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"504054fa_8f850d09","side":"PARENT","line":529,"in_reply_to":"01cc61c3_2bbd530d","updated":"2025-01-10 17:13:25.000000000","message":"\u003e there was no other test case which swift needs to use utf-8 encoded character in the header name\n\nIf you take out the whole overridden `begin`, you find several more tests that break. This fix is needed because I didn\u0027t update the message-framing state after parsing the rest of the headers.\n\n\u003e I wonder if swift would run into this in the production settings\n\nIt definitely can; you just need clients that send non-ascii header names.","commit_id":"94d3a5dee8122e14be0b00694d65c448ce47ea31"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"41fbcfed286fcfaa8cf275c38c2af1777803d176","unresolved":false,"context_lines":[{"line_number":526,"context_line":"            self.assertEqual([], lines)"},{"line_number":527,"context_line":""},{"line_number":528,"context_line":""},{"line_number":529,"context_line":"if six.PY2:"},{"line_number":530,"context_line":"    # The non-ASCII chars in metadata cause test hangs in"},{"line_number":531,"context_line":"    # _assert_all_nodes_have_frag because of https://bugs.python.org/issue37093"},{"line_number":532,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"219010f7_1ef6d593","side":"PARENT","line":529,"in_reply_to":"504054fa_8f850d09","updated":"2025-01-13 06:03:33.000000000","message":"I see, so if users have stored objects with utf-8 encoded names, swift does need to be back-compatible in order to serve those objects back to those users with python3.","commit_id":"94d3a5dee8122e14be0b00694d65c448ce47ea31"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"eaff9bc83af13f55f9978998898f5eb1076d8ffe","unresolved":true,"context_lines":[{"line_number":90,"context_line":"                \u0027Missing durable timestamp in %r\u0027 % self.frag_headers)"},{"line_number":91,"context_line":""},{"line_number":92,"context_line":"    def proxy_get(self):"},{"line_number":93,"context_line":"        # Use internal-client instead of python-swiftclient, since we can\u0027t"},{"line_number":94,"context_line":"        # handle UTF-8 headers properly w/ swiftclient."},{"line_number":95,"context_line":"        # Still a proxy-server tho!"},{"line_number":96,"context_line":"        status, headers, body \u003d self.int_client.get_object("}],"source_content_type":"text/x-python","patch_set":3,"id":"cf1d08a1_777ed15c","line":93,"updated":"2025-01-13 22:00:10.000000000","message":"with this fix, proxy-server is able to return those UTF-8 headers back to users and users should be able to access those objects. but I wonder if we need fix swiftclient as well to be able to handle those UTF-8 headers, or whether boto3 is able to.","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"7fd640607d9ec2fc742f9c4fd93a47b40bff58dd","unresolved":false,"context_lines":[{"line_number":90,"context_line":"                \u0027Missing durable timestamp in %r\u0027 % self.frag_headers)"},{"line_number":91,"context_line":""},{"line_number":92,"context_line":"    def proxy_get(self):"},{"line_number":93,"context_line":"        # Use internal-client instead of python-swiftclient, since we can\u0027t"},{"line_number":94,"context_line":"        # handle UTF-8 headers properly w/ swiftclient."},{"line_number":95,"context_line":"        # Still a proxy-server tho!"},{"line_number":96,"context_line":"        status, headers, body \u003d self.int_client.get_object("}],"source_content_type":"text/x-python","patch_set":3,"id":"faca3b32_16e30b4c","line":93,"in_reply_to":"3ae40ea1_4197f86c","updated":"2025-01-14 20:04:50.000000000","message":"Acknowledged","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"a4f09cd4a8c2a24c2e8ccbe8d8e6ea30c4602c4c","unresolved":true,"context_lines":[{"line_number":90,"context_line":"                \u0027Missing durable timestamp in %r\u0027 % self.frag_headers)"},{"line_number":91,"context_line":""},{"line_number":92,"context_line":"    def proxy_get(self):"},{"line_number":93,"context_line":"        # Use internal-client instead of python-swiftclient, since we can\u0027t"},{"line_number":94,"context_line":"        # handle UTF-8 headers properly w/ swiftclient."},{"line_number":95,"context_line":"        # Still a proxy-server tho!"},{"line_number":96,"context_line":"        status, headers, body \u003d self.int_client.get_object("}],"source_content_type":"text/x-python","patch_set":3,"id":"3ae40ea1_4197f86c","line":93,"in_reply_to":"cf1d08a1_777ed15c","updated":"2025-01-13 23:10:14.000000000","message":"boto3 (AFAIK) can\u0027t handle UTF-8 headers; fixing python-swiftclient would require monkey-patching stdlib or urllib3 or both. I don\u0027t really want to *encourage* people to use this feature, I just want to make sure I don\u0027t break people that already have used it.","commit_id":"06644f4e05339247e08f924867e652e9a17c2d21"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"105c57a60d4418aaa3047564445534b8729c152f","unresolved":true,"context_lines":[{"line_number":530,"context_line":"        # https://bugs.python.org/issue37093"},{"line_number":531,"context_line":"        raise unittest.SkipTest(\u0027This never got fixed on py3\u0027)"},{"line_number":532,"context_line":"        return b\u0027%s\\xc3\\xa8-%s\u0027 % ("},{"line_number":533,"context_line":"            prefix.encode(), str(uuid.uuid4()).encode())"},{"line_number":534,"context_line":""},{"line_number":535,"context_line":""},{"line_number":536,"context_line":"if __name__ \u003d\u003d \"__main__\":"}],"source_content_type":"text/x-python","patch_set":4,"id":"9321c94f_fa104184","side":"PARENT","line":533,"updated":"2025-01-28 17:24:28.000000000","message":"since tests are all py3 now maybe it would make more sense for this method to return a string?","commit_id":"128124cdd8ca09136d4988affd1bb8c5c1361fc1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"105c57a60d4418aaa3047564445534b8729c152f","unresolved":true,"context_lines":[{"line_number":434,"context_line":"        # ... but client HEAD succeeds"},{"line_number":435,"context_line":"        headers \u003d self.int_client.get_object_metadata("},{"line_number":436,"context_line":"            self.account,"},{"line_number":437,"context_line":"            self.container_name.decode(\u0027utf-8\u0027),"},{"line_number":438,"context_line":"            self.object_name.decode(\u0027utf-8\u0027))"},{"line_number":439,"context_line":"        for key in self.headers_post:"},{"line_number":440,"context_line":"            wsgi_key \u003d str_to_wsgi(key)"}],"source_content_type":"text/x-python","patch_set":4,"id":"ffbf3267_90901dfe","line":437,"updated":"2025-01-28 17:24:28.000000000","message":"i\u0027m not surprised the arguments to `get_object_metadata` want strings - i\u0027m surprised that `self.container_name` is bytes!?","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":true,"context_lines":[{"line_number":410,"context_line":"        self.assertEqual(503, cm.exception.http_status)"},{"line_number":411,"context_line":"        # ... but client GET succeeds"},{"line_number":412,"context_line":"        headers \u003d client.head_object(self.url, self.token, self.container_name,"},{"line_number":413,"context_line":"                                     self.object_name)"},{"line_number":414,"context_line":"        for key in self.headers_post:"},{"line_number":415,"context_line":"            self.assertIn(key, headers)"},{"line_number":416,"context_line":"            self.assertEqual(self.headers_post[key], headers[key])"}],"source_content_type":"text/x-python","patch_set":5,"id":"47172c0b_10de1f65","side":"PARENT","line":413,"updated":"2025-01-30 22:04:46.000000000","message":"so not that there\u0027s anything \"wrong\" with this response...\n\n```\nvagrant@saio:~$ curl \"http://127.0.0.4:6040/sdb4/37/AUTH_test/container-è-03c9cbb1-2068-4389-9a04-ce7860e9626b/object-è-aa0ed3e8-ce1a-474d-a938-bb5e7014daf8\" -H \u0027x-backend-storage-policy-index: 1\u0027 -I\nHTTP/1.1 200 OK\nContent-Type: application/octet-stream\nX-Object-Meta-è-0200C35E-C7Ee-4E51-9A86-399F906Fa2Dc: meta-bar-è-00cdd895-a3cc-44a0-939d-074ff7dd07d5\nX-Object-Sysmeta-Ec-Etag: 33e4cf4371633ca1535b1226fcb8b39e\nX-Object-Sysmeta-Ec-Content-Length: 3670016\nX-Object-Sysmeta-Ec-Frag-Index: 3\nX-Object-Sysmeta-Ec-Scheme: liberasurecode_rs_vand 4+2\nX-Object-Sysmeta-Ec-Segment-Size: 1048576\nEtag: \"117d3e4116b0ac18c23ecdf7ae982b0f\"\nLast-Modified: Thu, 30 Jan 2025 16:36:10 GMT\nX-Timestamp: 1738254969.62648\nX-Backend-Timestamp: 1738254969.62648\nX-Backend-Data-Timestamp: 1738254969.44737\nX-Backend-Durable-Timestamp: 1738254969.44737\nX-Backend-Fragments: {\"1738254969.44737\": [3]}\nContent-Length: 917824\nDate: Thu, 30 Jan 2025 19:37:37 GMT\n```\n\nbut in the vein of \"conservative in what you send\" - given that we understand how stdlib python http\u0027s header parsing breaks in the face of non-ascii characters - IF we can agree that \"getting http framing wrong\" is *worse* than \"losing some metadata\" - I wonder if it\u0027d be reasonable to go out of our way to to push the Content-Length header to the top of the list, or maybe object-meta down to the bottom?\n\n```\n(.venv) clayg@ThinkStation:~/Workspace/vagrant-swift-all-in-one/swift$ git diff swift/common/swob.py\ndiff --git a/swift/common/swob.py b/swift/common/swob.py\nindex 43d35959c..dd364c4be 100644\n--- a/swift/common/swob.py\n+++ b/swift/common/swob.py\n@@ -1504,7 +1504,12 @@ class Response(object):\n         if \u0027location\u0027 in self.headers and \\\n                 not env.get(\u0027swift.leave_relative_location\u0027):\n             self.location \u003d self.absolute_location()\n-        start_response(self.status, list(self.headers.items()))\n+        resp_header_list \u003d []\n+        priority_key \u003d \u0027Content-Length\u0027\n+        if priority_key in self.headers:\n+            resp_header_list.append((priority_key, self.headers.pop(priority_key)))\n+        resp_header_list.extend(self.headers.items())\n+        start_response(self.status, resp_header_list)\n         return self.response_iter\n```","commit_id":"128124cdd8ca09136d4988affd1bb8c5c1361fc1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":true,"context_lines":[{"line_number":169,"context_line":"                                 wsgi_to_str(headers[wsgi_key]))"},{"line_number":170,"context_line":"        # check all frags are intact, durable and have expected metadata"},{"line_number":171,"context_line":"        with self._annotate_failure_with_scenario(failed, non_durable):"},{"line_number":172,"context_line":"            frag_headers, frag_etags \u003d self._assert_all_nodes_have_frag()"},{"line_number":173,"context_line":"            self.assertEqual(self.frag_etags, frag_etags)"},{"line_number":174,"context_line":"            # self._frag_headers include X-Backend-Durable-Timestamp so this"},{"line_number":175,"context_line":"            # assertion confirms that the rebuilt frags are all durable"}],"source_content_type":"text/x-python","patch_set":5,"id":"d1b0514e_76cbd719","line":172,"updated":"2025-01-30 22:04:46.000000000","message":"it failed here; the node with the missing frag didn\u0027t get rebuilt and raised 404\n\nit\u0027s definitely \"stuck\" - but it doesn\u0027t seem like his peers are complaining very *loudly*\n\n```\nvagrant@saio:~$ swift-init object-reconstructor once -nv -c 2\nWARNING: Unable to modify max process limit.  Running as non-root?\nRemoving stale pid file /var/run/swift/object-reconstructor/2.pid.d\nRunning object-reconstructor once...(/etc/swift/object-server/2.conf.d)\nobject-reconstructor-6020: Starting 37766\nobject-reconstructor-6020: Running object reconstructor in script mode.\nobject-reconstructor-6020: Run listdir on /srv/node2/sdb6/objects-1/37\nobject-reconstructor-6020: Run listdir on /srv/node2/sdb2/objects-1/37\nobject-reconstructor-6020: Reconstruct frag #2 with frag indexes [1, 3, 5, 4]\nobject-reconstructor-6020: Error trying to rebuild /AUTH_test/container-è-03c9cbb1-2068-4389-9a04-ce7860e9626b/object-è-aa0ed3e8-ce1a-474d-a938-bb5e7014daf8 policy#1 frag#2: Timeout (10.0s)\nobject-reconstructor-6020: 127.0.0.1:6010/sdb5/37 Sent data length does not match content-length\nobject-reconstructor-6020: Reconstruct frag #2 with frag indexes [1, 4, 3, 5]\nobject-reconstructor-6020: Error trying to rebuild /AUTH_test/container-è-03c9cbb1-2068-4389-9a04-ce7860e9626b/object-è-aa0ed3e8-ce1a-474d-a938-bb5e7014daf8 policy#1 frag#2: Timeout (10.0s)\nobject-reconstructor-6020: 127.0.0.1:6010/sdb5/37 Sent data length does not match content-length\nobject-reconstructor-6020: 2/2 (100.00%) partitions reconstructed in 20.11s (0.10/sec, 0s remaining)\nobject-reconstructor-6020: 2 suffixes checked - 0.00% hashed, 100.00% synced\nobject-reconstructor-6020: Partition times: max 10.0517s, min 10.0513s, med 10.0517s\nobject-reconstructor-6020: Object reconstruction complete (once). (0.34 minutes)\nobject-reconstructor-6020: Exited 37766\nvagrant@saio:~$ cat /var/cache/swift/node2/object.recon\n{\"object_reconstruction_last\": 1738255564.514378, \"object_reconstruction_time\": 0.3352171301841736}\n```\n\nIt\u0027s actually the receiver that\u0027s complaining the most:\n```\nJan 30 16:46:04 saio object-server-6010: ssync subrequest failed with 499: PUT /sdb5/37/AUTH_test/container-%C3%A8-03c9cbb1-2068-4389-9a04-ce7860e9626b/object-%C3%A8-aa0ed3e8-ce1a-474d-a938-bb5e7014daf8 (b\u0027\u003chtml\u003e\u003ch1\u003eClient Disconnect\u003c/h1\u003e\u003cp\u003eThe client was disconnected during request.\u003c/p\u003e\u003c/html\u003e\u0027)\nJan 30 16:46:04 saio object-server-6010: STDERR: Traceback (most recent call last):\n  File \"/home/vagrant/.local/lib/python3.10/site-packages/eventlet/wsgi.py\", line 647, in handle_one_response\n    write(b\u0027\u0027)\n  File \"/home/vagrant/.local/lib/python3.10/site-packages/eventlet/wsgi.py\", line 570, in write\n    wfile.flush()\n  File \"/usr/lib/python3.10/socket.py\", line 723, in write\n    return self._sock.send(b)\n  File \"/home/vagrant/.local/lib/python3.10/site-packages/eventlet/greenio/base.py\", line 383, in send\n    return self._send_loop(self.fd.send, data, flags)\n  File \"/home/vagrant/.local/lib/python3.10/site-packages/eventlet/greenio/base.py\", line 370, in _send_loop\n    return send_method(data, *args)\nBrokenPipeError: [Errno 32] Broken pipe                                                                                                                                                                          \n```\n\nbasically \"hey you said you were going to send me this missing frag; why\u0027d you hang up!?\"","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[{"line_number":169,"context_line":"                                 wsgi_to_str(headers[wsgi_key]))"},{"line_number":170,"context_line":"        # check all frags are intact, durable and have expected metadata"},{"line_number":171,"context_line":"        with self._annotate_failure_with_scenario(failed, non_durable):"},{"line_number":172,"context_line":"            frag_headers, frag_etags \u003d self._assert_all_nodes_have_frag()"},{"line_number":173,"context_line":"            self.assertEqual(self.frag_etags, frag_etags)"},{"line_number":174,"context_line":"            # self._frag_headers include X-Backend-Durable-Timestamp so this"},{"line_number":175,"context_line":"            # assertion confirms that the rebuilt frags are all durable"}],"source_content_type":"text/x-python","patch_set":5,"id":"890914c0_f06f2142","line":172,"in_reply_to":"d1b0514e_76cbd719","updated":"2025-02-06 22:12:40.000000000","message":"Acknowledged","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":true,"context_lines":[{"line_number":175,"context_line":"            # assertion confirms that the rebuilt frags are all durable"},{"line_number":176,"context_line":"            self.assertEqual(self.frag_headers, frag_headers)"},{"line_number":177,"context_line":""},{"line_number":178,"context_line":"    def test_rebuild_missing_frags(self):"},{"line_number":179,"context_line":"        # build up a list of node lists to kill data from,"},{"line_number":180,"context_line":"        # first try a single node"},{"line_number":181,"context_line":"        # then adjacent nodes and then nodes \u003e1 node apart"}],"source_content_type":"text/x-python","patch_set":5,"id":"5a0b5ca0_8c4f330f","line":178,"updated":"2025-01-30 22:04:46.000000000","message":"when I reverted the fix this is the first one that ran and it failed.","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[{"line_number":175,"context_line":"            # assertion confirms that the rebuilt frags are all durable"},{"line_number":176,"context_line":"            self.assertEqual(self.frag_headers, frag_headers)"},{"line_number":177,"context_line":""},{"line_number":178,"context_line":"    def test_rebuild_missing_frags(self):"},{"line_number":179,"context_line":"        # build up a list of node lists to kill data from,"},{"line_number":180,"context_line":"        # first try a single node"},{"line_number":181,"context_line":"        # then adjacent nodes and then nodes \u003e1 node apart"}],"source_content_type":"text/x-python","patch_set":5,"id":"46f39132_501399e2","line":178,"in_reply_to":"5a0b5ca0_8c4f330f","updated":"2025-02-06 22:12:40.000000000","message":"Acknowledged","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9ab1e20ee1183649ac19d04367be14d9564a8afa","unresolved":true,"context_lines":[{"line_number":431,"context_line":"            client.get_object(self.url, self.token, self.container_name,"},{"line_number":432,"context_line":"                              self.object_name)"},{"line_number":433,"context_line":"        self.assertEqual(503, cm.exception.http_status)"},{"line_number":434,"context_line":"        # ... but client HEAD succeeds"},{"line_number":435,"context_line":"        headers \u003d self.int_client.get_object_metadata("},{"line_number":436,"context_line":"            self.account,"},{"line_number":437,"context_line":"            self.container_name.decode(\u0027utf-8\u0027),"}],"source_content_type":"text/x-python","patch_set":5,"id":"51447c99_7b60cac9","line":434,"updated":"2025-01-29 20:29:26.000000000","message":"is there a python-swiftclient bug to the effect of \"can\u0027t read all metadata from objects with non-ascii meta keys\" that we could reference here?","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":false,"context_lines":[{"line_number":431,"context_line":"            client.get_object(self.url, self.token, self.container_name,"},{"line_number":432,"context_line":"                              self.object_name)"},{"line_number":433,"context_line":"        self.assertEqual(503, cm.exception.http_status)"},{"line_number":434,"context_line":"        # ... but client HEAD succeeds"},{"line_number":435,"context_line":"        headers \u003d self.int_client.get_object_metadata("},{"line_number":436,"context_line":"            self.account,"},{"line_number":437,"context_line":"            self.container_name.decode(\u0027utf-8\u0027),"}],"source_content_type":"text/x-python","patch_set":5,"id":"9b0fe020_9e297d11","line":434,"in_reply_to":"51447c99_7b60cac9","updated":"2025-01-30 22:04:46.000000000","message":"YES!\n\nhttps://bugs.launchpad.net/python-swiftclient/+bug/2097034\n\nThis is in part why I think this is NBD - if anyone was actually doing this; in violation of the modern RFCs AND our API docs somehow *despite* the http.client bug (and presumably other modern language sdks) ... we\u0027d have know about it.  This probe test is making us maintain a behavior that hasn\u0027t worked reliably \"in the wild\" for a LONG time.","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"fc9f54b66e6c1b538a3a6ed36add8ff210d943a0","unresolved":true,"context_lines":[{"line_number":431,"context_line":"            client.get_object(self.url, self.token, self.container_name,"},{"line_number":432,"context_line":"                              self.object_name)"},{"line_number":433,"context_line":"        self.assertEqual(503, cm.exception.http_status)"},{"line_number":434,"context_line":"        # ... but client HEAD succeeds"},{"line_number":435,"context_line":"        headers \u003d self.int_client.get_object_metadata("},{"line_number":436,"context_line":"            self.account,"},{"line_number":437,"context_line":"            self.container_name.decode(\u0027utf-8\u0027),"}],"source_content_type":"text/x-python","patch_set":5,"id":"3c19d5c5_1cbbd40d","line":434,"in_reply_to":"51447c99_7b60cac9","updated":"2025-01-30 21:22:34.000000000","message":"https://bugs.launchpad.net/python-swiftclient/+bug/1904551 at least mentions the issue in some comments\n\nhttps://bugs.launchpad.net/python-swiftclient/+bug/1888276 talks about some of the *other* bad behaviors around unexpected header formats\n\nBut no, I don\u0027t think there\u0027s an existing swiftclient bug specifically about https://github.com/python/cpython/issues/81274","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"}],"test/unit/common/test_bufferedhttp.py":[{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"15db50003e08db2235373ac24c9cc0457d9519d8","unresolved":false,"context_lines":[{"line_number":136,"context_line":"            server.wait()"},{"line_number":137,"context_line":"        self.assertEqual(request[0], b\u0027GET /path HTTP/1.1\\r\\n\u0027)"},{"line_number":138,"context_line":""},{"line_number":139,"context_line":"    def test_get_with_non_ascii(self):"},{"line_number":140,"context_line":"        bindsock \u003d listen_zero()"},{"line_number":141,"context_line":"        request \u003d []"},{"line_number":142,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"44c66f67_94c81c41","line":139,"updated":"2025-01-14 05:15:07.000000000","message":"Good test case. Even though the new added code in BufferedHTTPResponse::begin() handles a few corner cases, I don\u0027t feel we need to cover them all at there, since the main purpose of this patch is to provide a basic and viable path for old users to access the stored unicode headers, and this unit test case verify that.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"15db50003e08db2235373ac24c9cc0457d9519d8","unresolved":false,"context_lines":[{"line_number":140,"context_line":"        bindsock \u003d listen_zero()"},{"line_number":141,"context_line":"        request \u003d []"},{"line_number":142,"context_line":""},{"line_number":143,"context_line":"        def accept():"},{"line_number":144,"context_line":"            with Timeout(3):"},{"line_number":145,"context_line":"                sock, addr \u003d bindsock.accept()"},{"line_number":146,"context_line":"                fp \u003d sock.makefile(\u0027rwb\u0027)"}],"source_content_type":"text/x-python","patch_set":4,"id":"899e248f_e5cc329a","line":143,"updated":"2025-01-14 05:15:07.000000000","message":"Neat, this is a mock http server using eventlet::spawn.","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"15db50003e08db2235373ac24c9cc0457d9519d8","unresolved":false,"context_lines":[{"line_number":179,"context_line":"            self.assertEqual(resp.length, 8)"},{"line_number":180,"context_line":"            self.assertEqual(resp.read(), b\u0027RESPONSE\u0027)"},{"line_number":181,"context_line":"            self.assertEqual(resp.read(), b\u0027\u0027)"},{"line_number":182,"context_line":"            self.assertEqual(resp.headers[\u0027X-Non-Ascii-M\\xc3\\xa9ta\u0027],"},{"line_number":183,"context_line":"                             \u0027\\xe1\\x88\\xb4\u0027)"},{"line_number":184,"context_line":"            # it\u0027s all HTTP/1.1 so we *could* pipeline, but we won\u0027t"},{"line_number":185,"context_line":"            conn.close()"}],"source_content_type":"text/x-python","patch_set":4,"id":"199165be_4e4d9212","line":182,"updated":"2025-01-14 05:15:07.000000000","message":"I changed the header name to lower case, and test still passed, the ``HTTPMessage`` based ``headers`` provides case-insensitive access to those stored HTTP headers.\n\n```\n            self.assertEqual(resp.headers[\u0027x-non-ascii-m\\xc3\\xa9ta\u0027],\n                             \u0027\\xe1\\x88\\xb4\u0027)\n```","commit_id":"29bc7664204eca797affb58f248306b1028977ba"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":true,"context_lines":[{"line_number":176,"context_line":""},{"line_number":177,"context_line":"            resp \u003d conn.getresponse()"},{"line_number":178,"context_line":"            self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)"},{"line_number":179,"context_line":"            self.assertEqual(resp.length, 8)"},{"line_number":180,"context_line":"            self.assertEqual(resp.read(), b\u0027RESPONSE\u0027)"},{"line_number":181,"context_line":"            self.assertEqual(resp.read(), b\u0027\u0027)"},{"line_number":182,"context_line":"            self.assertEqual(resp.headers[\u0027X-Non-Ascii-M\\xc3\\xa9ta\u0027],"}],"source_content_type":"text/x-python","patch_set":5,"id":"aff494e3_44d5bc26","line":179,"updated":"2025-01-30 22:04:46.000000000","message":"basically it seems like this assertion error prevents the `conn.close` and the Timeout trumps the AssertionError in the test runner.\n\nBut when I tried to make sure we always call `conn.close` before `server.wait` the `fp.readline()` would still block because `fp.closed \u003d\u003d False`; switching to `resp.nuke_from_orbit` would case the server to notice the socket was closed and stop waiting on it...\n\n```\n(.venv) clayg@ThinkStation:~/Workspace/vagrant-swift-all-in-one/swift$ git diff\ndiff --git a/test/unit/common/test_bufferedhttp.py b/test/unit/common/test_bufferedhttp.py\nindex 19144875d..6802a354d 100644\n--- a/test/unit/common/test_bufferedhttp.py\n+++ b/test/unit/common/test_bufferedhttp.py\n@@ -167,22 +167,24 @@ class TestBufferedHTTP(unittest.TestCase):\n             conn.putrequest(\u0027GET\u0027, \u0027/path\u0027)\n             conn.endheaders()\n             resp \u003d conn.getexpect()\n-            self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)\n-            self.assertEqual(resp.status, 100)\n-            self.assertEqual(resp.version, 11)\n-            self.assertEqual(resp.reason, \u0027Continue\u0027)\n-            # I don\u0027t think you\u0027re supposed to \"read\" a continue response\n-            self.assertRaises(AssertionError, resp.read)\n-\n-            resp \u003d conn.getresponse()\n-            self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)\n-            self.assertEqual(resp.length, 8)\n-            self.assertEqual(resp.read(), b\u0027RESPONSE\u0027)\n-            self.assertEqual(resp.read(), b\u0027\u0027)\n-            self.assertEqual(resp.headers[\u0027X-Non-Ascii-M\\xc3\\xa9ta\u0027],\n-                             \u0027\\xe1\\x88\\xb4\u0027)\n-            # it\u0027s all HTTP/1.1 so we *could* pipeline, but we won\u0027t\n-            conn.close()\n+            try:\n+                self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)\n+                self.assertEqual(resp.status, 100)\n+                self.assertEqual(resp.version, 11)\n+                self.assertEqual(resp.reason, \u0027Continue\u0027)\n+                # I don\u0027t think you\u0027re supposed to \"read\" a continue response\n+                self.assertRaises(AssertionError, resp.read)\n+\n+                resp \u003d conn.getresponse()\n+                self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)\n+                self.assertEqual(resp.length, 8)\n+                self.assertEqual(resp.read(), b\u0027RESPONSE\u0027)\n+                self.assertEqual(resp.read(), b\u0027\u0027)\n+                self.assertEqual(resp.headers[\u0027X-Non-Ascii-M\\xc3\\xa9ta\u0027],\n+                                 \u0027\\xe1\\x88\\xb4\u0027)\n+            finally:\n+                # it\u0027s all HTTP/1.1 so we *could* pipeline, but we won\u0027t\n+                resp.nuke_from_orbit()\n         finally:\n             server.wait()\n         self.assertEqual(request, [b\u0027GET /path HTTP/1.1\\r\\n\u0027, b\u0027\u0027])\n```\n\n.. but it might be more reasonable to just suppress the `server.wait()` Timeout and let the earlier AssertionError to bubble out:\n\n```\ndiff --git a/test/unit/common/test_bufferedhttp.py b/test/unit/common/test_bufferedhttp.py\nindex 19144875d..bd8e4d2f6 100644\n--- a/test/unit/common/test_bufferedhttp.py\n+++ b/test/unit/common/test_bufferedhttp.py\n@@ -141,7 +141,6 @@ class TestBufferedHTTP(unittest.TestCase):\n         request \u003d []\n\n         def accept():\n-            with Timeout(3):\n                 sock, addr \u003d bindsock.accept()\n                 fp \u003d sock.makefile(\u0027rwb\u0027)\n                 request.append(fp.readline())\n@@ -184,7 +183,8 @@ class TestBufferedHTTP(unittest.TestCase):\n             # it\u0027s all HTTP/1.1 so we *could* pipeline, but we won\u0027t\n             conn.close()\n         finally:\n-            server.wait()\n+            with Timeout(3, exception\u003dFalse):\n+                server.wait()\n         self.assertEqual(request, [b\u0027GET /path HTTP/1.1\\r\\n\u0027, b\u0027\u0027])\n\n     def test_closed_response(self):\n```\n\nWhich I think really brings the problem on master w/o this fix to light:\n\n```\n            resp \u003d conn.getresponse()\n            self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)\n\u003e           self.assertEqual(resp.length, 8)\nE           AssertionError: None !\u003d 8\n\nswift/test/unit/common/test_bufferedhttp.py:178: AssertionError\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d short test summary info \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nFAILED swift/test/unit/common/test_bufferedhttp.py::TestBufferedHTTP::test_get_with_non_ascii - AssertionError: None !\u003d 8\n```\n\nbecause of course the Content-Length header was truncated in the initial stdlib http.client begin parse","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[{"line_number":176,"context_line":""},{"line_number":177,"context_line":"            resp \u003d conn.getresponse()"},{"line_number":178,"context_line":"            self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)"},{"line_number":179,"context_line":"            self.assertEqual(resp.length, 8)"},{"line_number":180,"context_line":"            self.assertEqual(resp.read(), b\u0027RESPONSE\u0027)"},{"line_number":181,"context_line":"            self.assertEqual(resp.read(), b\u0027\u0027)"},{"line_number":182,"context_line":"            self.assertEqual(resp.headers[\u0027X-Non-Ascii-M\\xc3\\xa9ta\u0027],"}],"source_content_type":"text/x-python","patch_set":5,"id":"ef5cf6b9_c730cc71","line":179,"in_reply_to":"aff494e3_44d5bc26","updated":"2025-02-06 22:12:40.000000000","message":"I do think we should move the Timeout to get improved feedback when the test fails:\n\nhttps://review.opendev.org/c/openstack/swift/+/940935/1/test/unit/common/test_bufferedhttp.py","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":true,"context_lines":[{"line_number":180,"context_line":"            self.assertEqual(resp.read(), b\u0027RESPONSE\u0027)"},{"line_number":181,"context_line":"            self.assertEqual(resp.read(), b\u0027\u0027)"},{"line_number":182,"context_line":"            self.assertEqual(resp.headers[\u0027X-Non-Ascii-M\\xc3\\xa9ta\u0027],"},{"line_number":183,"context_line":"                             \u0027\\xe1\\x88\\xb4\u0027)"},{"line_number":184,"context_line":"            # it\u0027s all HTTP/1.1 so we *could* pipeline, but we won\u0027t"},{"line_number":185,"context_line":"            conn.close()"},{"line_number":186,"context_line":"        finally:"}],"source_content_type":"text/x-python","patch_set":5,"id":"0b94f8fd_28d2ffc8","line":183,"updated":"2025-01-30 22:04:46.000000000","message":"surprisingly (?) the existing inline \"fix\" for the header-prasing from master \"works\" for the non-ascii header keys *and* the content-length.\n\n```\n(Pdb) dict(resp.headers)\n{\u0027X-Non-Ascii-MÃ©ta\u0027: \u0027á\\x88´\u0027, \u0027Content-Length\u0027: \u00278\u0027}\n```\n\nthe \"problem\" is *only* in the \"other\" resp attributes that were incorrectly set in the upstream begin *before* we fixed the headers.  So fixing the header parsing right when they\u0027re read from the `fp` is definitely the cleanest way to get the correct behavior out of `begin()`.","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":true,"context_lines":[{"line_number":180,"context_line":"            self.assertEqual(resp.read(), b\u0027RESPONSE\u0027)"},{"line_number":181,"context_line":"            self.assertEqual(resp.read(), b\u0027\u0027)"},{"line_number":182,"context_line":"            self.assertEqual(resp.headers[\u0027X-Non-Ascii-M\\xc3\\xa9ta\u0027],"},{"line_number":183,"context_line":"                             \u0027\\xe1\\x88\\xb4\u0027)"},{"line_number":184,"context_line":"            # it\u0027s all HTTP/1.1 so we *could* pipeline, but we won\u0027t"},{"line_number":185,"context_line":"            conn.close()"},{"line_number":186,"context_line":"        finally:"}],"source_content_type":"text/x-python","patch_set":5,"id":"9e212369_0e2cdd71","line":183,"in_reply_to":"0b94f8fd_28d2ffc8","updated":"2025-02-06 22:12:40.000000000","message":"this is why it\u0027s a confusing pattern (in protocol \u0026 buffered http) to let the headers get parsed *wrong* and the reparse them *twice*; it might be better to just parse them once; correctly:\n\n940935: just parse_headers how we want | https://review.opendev.org/c/openstack/swift/+/940935","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"06bedb293021d638c52394a8ea8cb3cc597aca25","unresolved":true,"context_lines":[{"line_number":184,"context_line":"            # it\u0027s all HTTP/1.1 so we *could* pipeline, but we won\u0027t"},{"line_number":185,"context_line":"            conn.close()"},{"line_number":186,"context_line":"        finally:"},{"line_number":187,"context_line":"            server.wait()"},{"line_number":188,"context_line":"        self.assertEqual(request, [b\u0027GET /path HTTP/1.1\\r\\n\u0027, b\u0027\u0027])"},{"line_number":189,"context_line":""},{"line_number":190,"context_line":"    def test_closed_response(self):"}],"source_content_type":"text/x-python","patch_set":5,"id":"ffb39972_be034385","line":187,"updated":"2025-01-30 22:04:46.000000000","message":"with the change reverted:\n\n```\neventlet.timeout.Timeout: 3 seconds\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d short test summary info \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nFAILED swift/test/unit/common/test_bufferedhttp.py::TestBufferedHTTP::test_get_with_non_ascii - eventlet.timeout.Timeout: 3 seconds\n```","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":true,"context_lines":[{"line_number":184,"context_line":"            # it\u0027s all HTTP/1.1 so we *could* pipeline, but we won\u0027t"},{"line_number":185,"context_line":"            conn.close()"},{"line_number":186,"context_line":"        finally:"},{"line_number":187,"context_line":"            server.wait()"},{"line_number":188,"context_line":"        self.assertEqual(request, [b\u0027GET /path HTTP/1.1\\r\\n\u0027, b\u0027\u0027])"},{"line_number":189,"context_line":""},{"line_number":190,"context_line":"    def test_closed_response(self):"}],"source_content_type":"text/x-python","patch_set":5,"id":"80c65d0a_8a2f06ce","line":187,"in_reply_to":"ffb39972_be034385","updated":"2025-02-06 22:12:40.000000000","message":"I do think we should move the Timeout to get improved feedback when the test fails:\n\nhttps://review.opendev.org/c/openstack/swift/+/940935/1/test/unit/common/test_bufferedhttp.py","commit_id":"0f6ba89b5336f4d9adfbc4b3e2f5f9c4ed8f7482"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9772f65ebb96e9ae0bd6c07370464fac9277d65f","unresolved":false,"context_lines":[{"line_number":13,"context_line":"# implied."},{"line_number":14,"context_line":"# See the License for the specific language governing permissions and"},{"line_number":15,"context_line":"# limitations under the License."},{"line_number":16,"context_line":"import email.message"},{"line_number":17,"context_line":"import io"},{"line_number":18,"context_line":"from http.client import parse_headers"},{"line_number":19,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"c7858968_04f0c5fe","line":16,"in_reply_to":"56816f34_17527dd0","updated":"2025-02-06 02:04:02.000000000","message":"Done","commit_id":"b7f8263d1db2fbdf96178f6d5f76cd05c6f37b3c"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"aa36e5b853a91404611c49b7642194e795e54819","unresolved":true,"context_lines":[{"line_number":13,"context_line":"# implied."},{"line_number":14,"context_line":"# See the License for the specific language governing permissions and"},{"line_number":15,"context_line":"# limitations under the License."},{"line_number":16,"context_line":"import email.message"},{"line_number":17,"context_line":"import io"},{"line_number":18,"context_line":"from http.client import parse_headers"},{"line_number":19,"context_line":""}],"source_content_type":"text/x-python","patch_set":7,"id":"56816f34_17527dd0","line":16,"in_reply_to":"ebf39d24_a59c5f99","updated":"2025-02-04 21:58:01.000000000","message":"\u003e pep8: F401 \u0027email.message\u0027 imported but unused\n\nthere are still two pep8 failures...","commit_id":"b7f8263d1db2fbdf96178f6d5f76cd05c6f37b3c"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9772f65ebb96e9ae0bd6c07370464fac9277d65f","unresolved":false,"context_lines":[{"line_number":239,"context_line":"        # self.assertEqual([\u0027b\u0027], resp.headers.get_all(\u0027a\u0027))"},{"line_number":240,"context_line":""},{"line_number":241,"context_line":""},{"line_number":242,"context_line":"    def test_headers_setter_with_message(self):"},{"line_number":243,"context_line":"        msg \u003d parse_headers(io.BytesIO(b\u0027a: b\\na: bb\\nc: d\\n\\n\u0027))"},{"line_number":244,"context_line":"        self.assertEqual(\u0027\u0027, msg.get_payload())"},{"line_number":245,"context_line":"        resp \u003d bufferedhttp.BufferedHTTPResponse(None)"}],"source_content_type":"text/x-python","patch_set":7,"id":"b4515cfe_a39628ca","line":242,"in_reply_to":"2ba59971_668bba4b","updated":"2025-02-06 02:04:02.000000000","message":"Done","commit_id":"b7f8263d1db2fbdf96178f6d5f76cd05c6f37b3c"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"aa36e5b853a91404611c49b7642194e795e54819","unresolved":true,"context_lines":[{"line_number":239,"context_line":"        # self.assertEqual([\u0027b\u0027], resp.headers.get_all(\u0027a\u0027))"},{"line_number":240,"context_line":""},{"line_number":241,"context_line":""},{"line_number":242,"context_line":"    def test_headers_setter_with_message(self):"},{"line_number":243,"context_line":"        msg \u003d parse_headers(io.BytesIO(b\u0027a: b\\na: bb\\nc: d\\n\\n\u0027))"},{"line_number":244,"context_line":"        self.assertEqual(\u0027\u0027, msg.get_payload())"},{"line_number":245,"context_line":"        resp \u003d bufferedhttp.BufferedHTTPResponse(None)"}],"source_content_type":"text/x-python","patch_set":7,"id":"2ba59971_668bba4b","line":242,"in_reply_to":"ee330606_03dc37ca","updated":"2025-02-04 21:58:01.000000000","message":"\u003e pep8: E303 too many blank lines (2)\n\nand this one.","commit_id":"b7f8263d1db2fbdf96178f6d5f76cd05c6f37b3c"}],"test/unit/common/test_internal_client.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"5c59dbdf44318c9a4e6092e9a8b9a5d8d3659b38","unresolved":true,"context_lines":[{"line_number":971,"context_line":"        resp_headers \u003d {"},{"line_number":972,"context_line":"            metadata_prefix + swob.str_to_wsgi(\u0027\\N{SNOWFLAKE}\u0027): \u00271\u0027,"},{"line_number":973,"context_line":"            metadata_prefix + swob.str_to_wsgi("},{"line_number":974,"context_line":"                \u0027\\N{LATIN SMALL LETTER E WITH ACUTE}\u0027): \u00272\u0027,"},{"line_number":975,"context_line":"            \u0027%sThree\u0027 % (metadata_prefix): \u00273\u0027,"},{"line_number":976,"context_line":"            \u0027some_header-four\u0027: \u00274\u0027,"},{"line_number":977,"context_line":"            \u0027Some_header-five\u0027: \u00275\u0027,"}],"source_content_type":"text/x-python","patch_set":8,"id":"f97637c0_156b1aa6","line":974,"updated":"2025-02-05 22:51:33.000000000","message":"do you have a strong sense of how \"correct behavior\" would be defined if the user set a header with utf8 encoded bytes for `\u0027\\N{LATIN CAPITAL LETTER E WITH ACUTE}\u0027`\n\n```\nE       - {\u0027three\u0027: \u00273\u0027, \u0027Ã©\u0027: \u00272\u0027, \u0027â\\x9d\\x84\u0027: \u00271\u0027}\nE       ?                  ^\nE       \nE       + {\u0027three\u0027: \u00273\u0027, \u0027Ã\\x89\u0027: \u00272\u0027, \u0027â\\x9d\\x84\u0027: \u00271\u0027}\nE       ?                  ^^^^\n```\n\n... it seems like internal_client would want to \"normalize\" to lower?  Python seems to be pretty sure that\u0027s reasonable as long as we\u0027re just dealing with unicode strings:\n\n```\n(Pdb) print(\u0027\\N{LATIN CAPITAL LETTER E WITH ACUTE}\u0027.lower())\né\n```\n\nI get that `lower` on the \"bytes decoded as latin-1\" (or \"wsgi\") string is non-sense... \n\n```\nE       - {\u0027three\u0027: \u00273\u0027, \u0027Ã©\u0027: \u00272\u0027, \u0027â\\x9d\\x84\u0027: \u00271\u0027}\nE       + {\u0027three\u0027: \u00273\u0027, \u0027â\\x9d\\x84\u0027: \u00271\u0027, \u0027ã©\u0027: \u00272\u0027}\n```","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[{"line_number":971,"context_line":"        resp_headers \u003d {"},{"line_number":972,"context_line":"            metadata_prefix + swob.str_to_wsgi(\u0027\\N{SNOWFLAKE}\u0027): \u00271\u0027,"},{"line_number":973,"context_line":"            metadata_prefix + swob.str_to_wsgi("},{"line_number":974,"context_line":"                \u0027\\N{LATIN SMALL LETTER E WITH ACUTE}\u0027): \u00272\u0027,"},{"line_number":975,"context_line":"            \u0027%sThree\u0027 % (metadata_prefix): \u00273\u0027,"},{"line_number":976,"context_line":"            \u0027some_header-four\u0027: \u00274\u0027,"},{"line_number":977,"context_line":"            \u0027Some_header-five\u0027: \u00275\u0027,"}],"source_content_type":"text/x-python","patch_set":8,"id":"958770c7_411a7671","line":974,"in_reply_to":"f97637c0_156b1aa6","updated":"2025-02-06 22:12:40.000000000","message":"Tim has taught me that it would definately *incorrect* this test returned `\u0027\\N{LATIN SMALL LETTER E WITH ACUTE}\u0027` if the backend responded with `\u0027\\N{LATIN CAPITAL LETTER E WITH ACUTE}\u0027`, even tho:\n\n```\n\u003e\u003e\u003e \u0027\\N{LATIN CAPITAL LETTER E WITH ACUTE}\u0027.lower() \u003d\u003d \u0027\\N{LATIN SMALL LETTER E WITH ACUTE}\u0027\nTrue\n```\n\n.. because \"reasons\":\n\n940840: WIP: Normalize HTTP headers consistently | https://review.opendev.org/c/openstack/swift/+/940840","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"5c59dbdf44318c9a4e6092e9a8b9a5d8d3659b38","unresolved":true,"context_lines":[{"line_number":972,"context_line":"            metadata_prefix + swob.str_to_wsgi(\u0027\\N{SNOWFLAKE}\u0027): \u00271\u0027,"},{"line_number":973,"context_line":"            metadata_prefix + swob.str_to_wsgi("},{"line_number":974,"context_line":"                \u0027\\N{LATIN SMALL LETTER E WITH ACUTE}\u0027): \u00272\u0027,"},{"line_number":975,"context_line":"            \u0027%sThree\u0027 % (metadata_prefix): \u00273\u0027,"},{"line_number":976,"context_line":"            \u0027some_header-four\u0027: \u00274\u0027,"},{"line_number":977,"context_line":"            \u0027Some_header-five\u0027: \u00275\u0027,"},{"line_number":978,"context_line":"        }"}],"source_content_type":"text/x-python","patch_set":8,"id":"2b48dee2_6c5324b8","line":975,"updated":"2025-02-05 22:51:33.000000000","message":"why not spell this one as `metadata_prefix + xxx` as well?\n\noic, this is borrowing the pattern from `test_get_metadata`\n\nIMHO that pattern sort of stinks - it has to subclass the UUT and overwrite make_request?  What are you even testing at that point...","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":false,"context_lines":[{"line_number":972,"context_line":"            metadata_prefix + swob.str_to_wsgi(\u0027\\N{SNOWFLAKE}\u0027): \u00271\u0027,"},{"line_number":973,"context_line":"            metadata_prefix + swob.str_to_wsgi("},{"line_number":974,"context_line":"                \u0027\\N{LATIN SMALL LETTER E WITH ACUTE}\u0027): \u00272\u0027,"},{"line_number":975,"context_line":"            \u0027%sThree\u0027 % (metadata_prefix): \u00273\u0027,"},{"line_number":976,"context_line":"            \u0027some_header-four\u0027: \u00274\u0027,"},{"line_number":977,"context_line":"            \u0027Some_header-five\u0027: \u00275\u0027,"},{"line_number":978,"context_line":"        }"}],"source_content_type":"text/x-python","patch_set":8,"id":"bcf34755_61db4906","line":975,"in_reply_to":"2b48dee2_6c5324b8","updated":"2025-02-06 22:12:40.000000000","message":"Acknowledged","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"5c59dbdf44318c9a4e6092e9a8b9a5d8d3659b38","unresolved":true,"context_lines":[{"line_number":984,"context_line":""},{"line_number":985,"context_line":"        client \u003d InternalClient(self, path, resp_headers)"},{"line_number":986,"context_line":"        metadata \u003d client._get_metadata(path, metadata_prefix)"},{"line_number":987,"context_line":"        self.assertEqual(exp_metadata, metadata)"},{"line_number":988,"context_line":"        self.assertEqual(1, client.make_request_called)"},{"line_number":989,"context_line":""},{"line_number":990,"context_line":"    def test_get_metadata_invalid_status(self):"}],"source_content_type":"text/x-python","patch_set":8,"id":"a0545dee_d6325c25","line":987,"updated":"2025-02-05 22:51:33.000000000","message":"oh bummer, so we return the confusingly \"wsgi encoded\" strings instead of the \"properly decoded\" metadata?\n\nHow does client.set_object_metadata behave if you give it a string that will blow up... torlolol\n\n```\nvagrant@saio:~$ cat /vagrant/.scratch/internal-account-post.py\nfrom argparse import ArgumentParser\nfrom swift.common.internal_client import InternalClient\n\nparser \u003d ArgumentParser()\nparser.add_argument(\u0027--internal-client-conf\u0027,\n        default\u003d\u0027/etc/swift/internal-client.conf\u0027,\n        help\u003d\u0027path to internal-client configs\u0027)\nparser.add_argument(\u0027account\u0027, help\u003d\u0027name of account\u0027)\nparser.add_argument(\u0027-H\u0027, \u0027--header\u0027, action\u003d\u0027append\u0027,\n        help\u003d\u0027header key:value to post\u0027)\n\nargs \u003d parser.parse_args()\nswift \u003d InternalClient(args.internal_client_conf, \u0027internal-post\u0027, 1)\nmetadata \u003d dict(h.split(\u0027:\u0027, 1) for h in args.header or [])\nprint(\u0027setting %r on %s\u0027 % (metadata, args.account))\nswift.set_account_metadata(args.account, metadata)\nprint(\u0027done\u0027)\nvagrant@saio:~$ python3 /vagrant/.scratch/internal-account-post.py -H \u0027X-Account-Meta-☃: Frosty\u0027 AUTH_test\nsetting {\u0027X-Account-Meta-☃\u0027: \u0027 Frosty\u0027} on AUTH_test\nTraceback (most recent call last):\n  File \"/vagrant/.scratch/internal-account-post.py\", line 16, in \u003cmodule\u003e\n    swift.set_account_metadata(args.account, metadata)\n  File \"/vagrant/swift/swift/common/internal_client.py\", line 518, in set_account_metadata\n    self._set_metadata(\n  File \"/vagrant/swift/swift/common/internal_client.py\", line 392, in _set_metadata\n    self.handle_request(\u0027POST\u0027, path, headers, acceptable_statuses)\n  File \"/vagrant/swift/swift/common/internal_client.py\", line 256, in handle_request\n    resp \u003d self.make_request(*args, **kwargs)\n  File \"/vagrant/swift/swift/common/internal_client.py\", line 214, in make_request\n    req \u003d Request.blank(\n  File \"/vagrant/swift/swift/common/swob.py\", line 937, in blank\n    req.headers[key] \u003d val\n  File \"/vagrant/swift/swift/common/swob.py\", line 253, in __setitem__\n    self.environ[header_to_environ_key(key)] \u003d str(value)\n  File \"/vagrant/swift/swift/common/swob.py\", line 216, in header_to_environ_key\n    real_header \u003d wsgi_to_bytes(header_name)\n  File \"/vagrant/swift/swift/common/swob.py\", line 276, in wsgi_to_bytes\n    return wsgi_str.encode(\u0027latin1\u0027)\nUnicodeEncodeError: \u0027latin-1\u0027 codec can\u0027t encode character \u0027\\u2603\u0027 in position 15: ordinal not in range(256)\n```","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"80b8543eefbba9413b28eddcfee02c6c21535a83","unresolved":true,"context_lines":[{"line_number":984,"context_line":""},{"line_number":985,"context_line":"        client \u003d InternalClient(self, path, resp_headers)"},{"line_number":986,"context_line":"        metadata \u003d client._get_metadata(path, metadata_prefix)"},{"line_number":987,"context_line":"        self.assertEqual(exp_metadata, metadata)"},{"line_number":988,"context_line":"        self.assertEqual(1, client.make_request_called)"},{"line_number":989,"context_line":""},{"line_number":990,"context_line":"    def test_get_metadata_invalid_status(self):"}],"source_content_type":"text/x-python","patch_set":8,"id":"0e707d64_2646281c","line":987,"in_reply_to":"66856e88_9577b62d","updated":"2025-02-06 22:12:40.000000000","message":"fixing `get` and leaving `set` still broken feels like a \"half fix\" - and I\u0027m just left questioning \"why change this at all\" - is it a real problem?  Or just something we made up trying to get this test passing?\n\n940897: sq: just fix buffered http \u0026 the probe test | https://review.opendev.org/c/openstack/swift/+/940897\n\nIf we want to consider more seriously the impact of changing InternalClient I\u0027d prefer to do that in the context of fixing our inconsistent header normalization more broadly:\n\n940840: WIP: Normalize HTTP headers consistently | https://review.opendev.org/c/openstack/swift/+/940840","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"},{"author":{"_account_id":34930,"name":"Jianjian Huo","email":"jhuo@nvidia.com","username":"jhuo"},"change_message_id":"9772f65ebb96e9ae0bd6c07370464fac9277d65f","unresolved":true,"context_lines":[{"line_number":984,"context_line":""},{"line_number":985,"context_line":"        client \u003d InternalClient(self, path, resp_headers)"},{"line_number":986,"context_line":"        metadata \u003d client._get_metadata(path, metadata_prefix)"},{"line_number":987,"context_line":"        self.assertEqual(exp_metadata, metadata)"},{"line_number":988,"context_line":"        self.assertEqual(1, client.make_request_called)"},{"line_number":989,"context_line":""},{"line_number":990,"context_line":"    def test_get_metadata_invalid_status(self):"}],"source_content_type":"text/x-python","patch_set":8,"id":"66856e88_9577b62d","line":987,"in_reply_to":"a0545dee_d6325c25","updated":"2025-02-06 02:04:02.000000000","message":"oh, this is in the ``set_metadata`` path while the current changes only fix ``get_metadata`` in the internal client. it could be a follow-up patch if we decide to support the unicode header name in the set path as well, but IMHO maybe we shouldn\u0027t support the set path anymore even with internal client, if we are going to close the door eventually.","commit_id":"d125634b3ee8795b3ad9a2c017c60753d94ec08c"}]}
