Package: wsgiprox
wsgiproxwsgiproxis a Python WSGI middleware for adding HTTP and HTTPS proxy support to a WSGI application.The library accepts HTTP and HTTPS proxy connections, and routes them to a designated prefix.UsageFor example, given aWSGIcallableapplication, the middleware could be defined as follows:fromwsgiprox.wsgiproximportWSGIProxMiddlewareapplication=WSGIProxMiddleware(application,'/prefix/','wsgiprox')With the above configuration, the middleware is configured to add a prefix of/prefix/to any url, unless it is to the proxy hostwsgiprox.  Assuming a WSGI server running on port 8080, the middleware would translate HTTP/S proxy connections to a non-proxy WSGI request, and pass to the wrapped application:Proxy Request:curl-x"localhost:8080""http://example.com/path/file.html?A=B"Becomes equivalent to:curl"http://localhost:8080/prefix/http://example.com/path/file.html?A=B"Proxy Request:curl-k-x"localhost:8080""https://example.com/path/file.html?A=B"Becomes equivalent to:curl"http://localhost:8080/prefix/https://example.com/path/file.html?A=B"Proxy Request to proxy host:curl-k-x"localhost:8080""https://wsgiprox/path/file.html?A=B"Not adding prefix forwsgiprox, becomes equivalent to:curl-H"Host: wsgiprox""http://localhost:8080/path/file.html?A=B"All standard WSGIenvironfields are set to the expected values for the translated url.When a request passes through wsgiprox middleware,environ['wsgiprox.proxy_host']is set to the proxy host.
In this example, the WSGI app could check thatenviron.get('wsgiprox.proxy_host')== 'wsgiprox'to ensure that it was a proxy request. If the request is to the proxy host itself, then it is passed to the WSGI app without prefixing, andenviron['wsgiprox.proxy_host']==environ['HTTP_HOST']Custom ResolversThe providedFixedResolversimply prepends a fixed prefix to each url. A custom resolver could compute the final url in a different way. The resolver instance is called with the full url, and the original WSGIenviron. The result is the translatedREQUEST_URIthat is passed to the WSGI applictaion.Seeresolvers.pyfor all available resolvers.For example, the following Resolver translates the url to a custom prefix based on the remote IP of the original request.classIPResolver(object):def__call__(self,url,environ):return'/'+environ['REMOTE_ADDR']+'/'+urlapplication=WSGIProxMiddleware(application,IPResolver())HTTPS CATo support HTTPS proxy,wsgiproxcreates a custom CA (Certificate Authority), which must be accepted by the client (or it must ignore cert verification as with the-koption in CURL)By default,wsgiproxlooks for CA .pem at:<workingdir>/ca/wsgiprox-ca.pemand auto-creates this bundle using thecertauthlibrary.The CA name and CA root cert filename can also be specified explicitly viaproxy_optionsdict.By default, the following options are used:WSGIProxMiddleware(...,proxy_options={ca_name='wsgiprox https proxy CA',ca_file='./ca/wsgiprox-ca.pem'})The generatedwsgiprox-ca.pemcan be imported directly into most browsers directly as a trusted certificate authority, allowing the browser to accept HTTPS content proxied throughwsgiproxDownloading CertsThe CA cert can be downloaded directly from the proxy directly. This allows for quick installation into a client/browser.curl-x"localhost:8080"http://wsgiprox/download/pemwill download in PEM format (for most platforms)curl-x"localhost:8080"http://wsgiprox/download/p12will download in PKCS12 format (for Windows)The download host is the same as proxy main host, though can be changed viadownload_hostparam to WSGIProxMiddleware constructor.Custom Proxy Host AppsIt’s is also possible to configure a custom WSGI app per proxy host, eg:curl-x"localhost:8080"https://proxy-app-1/path/is passed toproxy-app-1curl-x"localhost:8080"https://proxy-app-2/foois passed toproxy-app-2This can be done via:fromwsgiprox.wsgiproximportWSGIProxMiddlewareproxy_apps={"proxy-app-1":ProxyApp1WSGI(),"proxy-app-2":ProxyApp2WSGI(),"proxy-alias":None,}application=WSGIProxMiddleware(application,proxy_apps=apps)All other requests, or any requests not handled by the proxy app, are passed to the mainapplication.In the last case, since there is no proxy app, the request is passed directly to wrapped application.
Thewsgiprox.proxy_hostwould be set to'proxy-alias'instead of the default'wsgiprox', allowing the application to differentiate handling based on the value ofwsgiprox.proxy_host.Internally, theproxy_appsdict is used to configure the cert downloader app and default proxy host:proxy_apps['proxy_host']=Noneproxy_apps['download_host']=CertDownloader(self.ca)Websocketswsgiproxoptionally also supports proxying websockets, both unencrypedws://and via TLSwss://. The websockets proxy functionality has primarily been tested with and requires thegevent-websocketlibrary, and assumes that the wrapped WSGI application is also using this library for websocket support. Other implementations are not yet supported.To enable websocket proxying, install withpip installwsgiprox[gevent-websocket]which will installgevent-websocket.
To disable websocket proxying even withgevent-websocketinstalled, addproxy_options={'enable_websockets':False}See thetest suitefor additional details.How it Works / A note about WSGIwsgiproxsupports several different proxying methods:HTTP direct proxy, no tunnelHTTP CONNECT tunnel for websockets, no SSLHTTP CONNECT tunnel with SSL (also supports websockets)For regular HTTP proxy, wsgiprox simply rewrites a host-qualifed request such asGEThttp://example.com/, and passes it along to underlying WSGI app.The other proxy methods involve the HTTPCONNECTverb and explicitly establishing a tunnel using the underlying socket. For HTTPS/SSL proxying, an SSL socket is established over the tunnel, while HTTP websocket proxy uses the underlying socket directly.The system thus relies on being able to access the underyling socket for the connection. As WSGI spec does not provide a way to do this,wsgiproxis not guaranteed to work under any WSGI server. The CONNECT verb creates a tunnel, and the tunneled connection is what is passed to the wrapped WSGI application. This is non-standard behavior and may not work on all WSGI servers.This middleware has been tested primarily with gevent WSGI server and uWSGI.There is also support for gunicorn and wsgiref, as they provide a way to access the underlying success. If the underlying socket can not be accessed, theCONNECTverb will fail with a 405.It may be possible to extend support to additional WSGI servers by extendingWSGIProxMiddleware.get_raw_socket()to be able to find the underlying socket.InspirationThis project draws inspiration from a lot of previous efforts.Much of the functionality is a refactoring and spin-off of the proxy functionality inpywb, which is built on top of standalone CA handling librarycertauth.certauth was refactored from an earlier implementation inwarcprox(which also inspired this name!).The certificate download feature was inspired by a similar feature available inmitmproxLicensewsgiproxis licensed under the Apache 2.0 License and is part of the
Webrecorder project.SeeNOTICEandLICENSEfor details.