You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I want to access the headers returned by the CONNECT HTTP call.
I'm achieving this now by the following grotesque code. I wish there were a supported way to do this, but unfortunately the current implementation throws these headers away.
classWrapperHTTPProxy(wrapt.ObjectProxy):
asyncdefsend(
self,
request: AsyncRequest,
verify: VerifyTypes=None,
cert: CertTypes=None,
timeout: TimeoutTypes=None,
) ->AsyncResponse:
# copy of send from httpx.dispatch.HTTPConnectionasyncdefacquire_connection(self, origin: Origin) ->HTTPConnection:
# copy of acquire_connection from httpx.dispatch.HTTPProxyasyncdeftunnel_connection(self, origin: Origin) ->HTTPConnection:
# copy of tunnel_connection from httpx.dispatch.HTTPProxyasyncdefrequest_tunnel_proxy_connection(self, origin: Origin) ->HTTPConnection:
# copy of request_tunnel_proxy_connection from httpx.dispatch.HTTPProxy# with new code at the endifnot (200<=proxy_response.status_code<=299):
awaitproxy_response.read()
raisehttpx.ProxyError(
f"Non-2XX response received from HTTP proxy "f"({proxy_response.status_code})",
request=proxy_request,
response=proxy_response,
)
else:
proxy_response.on_close=Noneawaitproxy_response.read()
# NEW CODE: send the proxy response headers to a callbackifwrapper._self_headers_callback:
wrapper._self_headers_callback(proxy_response.headers)
returnconnectionclassMyClassThatDoesStuff:
latest_headers=Nonedefafter_session_is_set_up(self):
https_proxy=self.httpx_session.proxies['https']
self.proxy_wrapper=WrapperHTTPProxy(https_proxy)
self.httpx_session.proxies['https'] =self.proxy_wrapperdefcb(headers):
self.latest_headers=headersself.proxy_wrapper._self_headers_callback=cb
Thanks for considering this, Tom. I'm not familiar enough with the httpx API and the design principles behind it to offer an API suggestion at this point. As I use httpx more, I'll give this some thought.
I think reading the CONNECT response headers is also quite difficult in requests / urllib3. For example, there's this open issue on httplib:
This establishes that, while this is a niche need, there's at least one other person who wanted to access these headers... and that it's likely harder to do in urllib3 and requests than in httpx.
Thanks for the suggestion to subclass HTTPProxy. I'll give that a go.
Thanks for the suggestion to subclass HTTPProxy. I'll give that a go.
Great - probably worth following up here with how you get on there.
Perhaps there's something here that we could come back to, but at the moment it looks more likely that we should instead make sure that it's not too painful to do this with a custom proxy class.
Okay — subclassing HTTPProxy is significantly better. Rather than 90 lines of code copied, and using Wrapt, it's now more standard Python and 30 lines of code copied.
classResponseHeaderCapturingProxy(httpx.HTTPProxy):
def__init__(self, proxy_url, backend=None):
super().__init__(proxy_url, backend=backend)
self.response_headers=Nonedefproxy_response_hook(self, response):
self.response_headers=response.headersasyncdefrequest_tunnel_proxy_connection(self, origin: httpx.Origin) ->httpx.HTTPConnection:
"""Creates an HTTPConnection by setting up a TCP tunnel"""proxy_headers=self.proxy_headers.copy()
proxy_headers.setdefault("Accept", "*/*")
proxy_request=httpx.AsyncRequest(
method="CONNECT", url=self.proxy_url.copy_with(), headers=proxy_headers
)
proxy_request.url.full_path=f"{origin.host}:{origin.port}"awaitself.max_connections.acquire()
connection=httpx.HTTPConnection(
self.proxy_url.origin,
verify=self.verify,
cert=self.cert,
timeout=self.timeout,
backend=self.backend,
http_versions=["HTTP/1.1"], # Short-lived 'connection'trust_env=self.trust_env,
release_func=self.release_connection,
)
self.active_connections.add(connection)
# See if our tunnel has been opened successfullyproxy_response=awaitconnection.send(proxy_request)
self.proxy_response_hook(proxy_response) # Hook inserted into my subclassifnot (200<=proxy_response.status_code<=299):
awaitproxy_response.read()
raisehttpx.ProxyError(
f"Non-2XX response received from HTTP proxy "f"({proxy_response.status_code})",
request=proxy_request,
response=proxy_response,
)
else:
proxy_response.on_close=Noneawaitproxy_response.read()
returnconnection
It would be nice to be able to copy less code. Perhaps the hook I inserted into my subclass could be added to HTTPProxy along with a no-op implementation of proxy_response_hook(response)?
For my purposes, a synchronous method is fine, but for flexibility I guess an async hook method gives more choice.
One other thing I'd like to mention: while I was looking for others wanting to use this kind of feature in Python http libraries, I came across the idea of having an option on an HTTP Proxy to either
ignore headers from the proxy response
combine headers from the proxy response into the regular http response
Gotcha. I don't have much of a gauge on how useful this would likely be to other users or not. Is that a typical thing to do? I guess we might want to wait and see if a similar issue comes up again, and if so figure out if there's much commonality?
It is needed by anyone using certain HTTP proxy services that do specific things like rotate IP addresses, where you want to give feedback about the proxying independently of the requesting. For example, https://luminati.io/ or https://www.proxymesh.com/
Activity
lovelydinosaur commentedon Nov 30, 2019
On the face of it this doesn't immediately look to me like something that we ought to be supporting via our public API.
It's possible that I've made an incorrect assessment here. There's a couple of things that could change my mind on this...
requests/urllib3?In the meantime I'd suggest you support this by subclassing
HTTPProxy, adding your custom behavior, and using your custom proxy with the client...SteveAlexander commentedon Nov 30, 2019
Thanks for considering this, Tom. I'm not familiar enough with the
httpxAPI and the design principles behind it to offer an API suggestion at this point. As I usehttpxmore, I'll give this some thought.I think reading the
CONNECTresponse headers is also quite difficult inrequests/urllib3. For example, there's this open issue onhttplib:https://bugs.python.org/issue24964
And, related to this, a stackoverflow question, with some background about why this might be a useful feature in some circumstances.
https://stackoverflow.com/questions/39068998/reading-connect-headers
Finally, an implementation in
urllib3showing that, indeed, it's not straightforward:https://gist.github.com/bpartridge/9c758c5e70222bac6ce6e1db7bb4d8ea
This establishes that, while this is a niche need, there's at least one other person who wanted to access these headers... and that it's likely harder to do in
urllib3andrequeststhan inhttpx.Thanks for the suggestion to subclass
HTTPProxy. I'll give that a go.lovelydinosaur commentedon Nov 30, 2019
Great - probably worth following up here with how you get on there.
Perhaps there's something here that we could come back to, but at the moment it looks more likely that we should instead make sure that it's not too painful to do this with a custom proxy class.
SteveAlexander commentedon Nov 30, 2019
Okay — subclassing
HTTPProxyis significantly better. Rather than 90 lines of code copied, and usingWrapt, it's now more standard Python and 30 lines of code copied.It would be nice to be able to copy less code. Perhaps the hook I inserted into my subclass could be added to
HTTPProxyalong with a no-op implementation ofproxy_response_hook(response)?For my purposes, a synchronous method is fine, but for flexibility I guess an async hook method gives more choice.
SteveAlexander commentedon Nov 30, 2019
One other thing I'd like to mention: while I was looking for others wanting to use this kind of feature in Python http libraries, I came across the idea of having an option on an HTTP Proxy to either
That would have served my need in this case too.
lovelydinosaur commentedon Dec 2, 2019
Gotcha. I don't have much of a gauge on how useful this would likely be to other users or not. Is that a typical thing to do? I guess we might want to wait and see if a similar issue comes up again, and if so figure out if there's much commonality?
SteveAlexander commentedon Dec 2, 2019
It's kind of niche, I guess.
It is needed by anyone using certain HTTP proxy services that do specific things like rotate IP addresses, where you want to give feedback about the proxying independently of the requesting. For example, https://luminati.io/ or https://www.proxymesh.com/