The story of HTTP statuses HTTP 1.0 and 1.1 302, 303 and 307 is a bit complicated. Basically you see the expected and documented behavior (you can also see this answer for a more detailed description):
The standard implementation of this method does not strictly follow RFC 2616 , which states that 301 and 302 responses to POST requests should not be automatically redirected without user confirmation. In fact, browsers allow you to automatically redirect these responses, changing POST to GET , and the default implementation reproduces this behavior.
And you go right, but redefine the wrong methods. Here's the source urllib2.HTTPRedirectHandler.redirect_request :
def redirect_request(self, req, fp, code, msg, headers, newurl): """Return a Request or None in response to a redirect. ... Return None if you can't but another Handler might. """ m = req.get_method() if (code in (301, 302, 303, 307) and m in ("GET", "HEAD") or code in (301, 302, 303) and m == "POST"): # ... newurl = newurl.replace(' ', '%20') newheaders = dict((k,v) for k,v in req.headers.items() if k.lower() not in ("content-length", "content-type") ) return Request(newurl, headers=newheaders, origin_req_host=req.get_origin_req_host(), unverifiable=True) else: raise HTTPError(req.get_full_url(), code, msg, headers, fp)
A few observations here. It does not pass data , so a new GET request. It filters out the content-length and content-type headers, which are necessary for a proper POST . If fact, in my example req.headers is an empty dict, so I turned to req.header_items() (see unredirected_hdrs ). Moreover, it does not handle POST and 307 redirects.
This is where the correct implementation of the redirection handler to redirect the POST and 302 runs. Here, the full CherryPy simulation is done (before pip install cherrypy ).
#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib2 from urllib2 import HTTPRedirectHandler, Request import cherrypy config = { 'global' : { 'server.socket_host' : '127.0.0.1', 'server.socket_port' : 8080, 'server.thread_pool' : 8 } } class RedirectHandler(HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): if code == 302 and req.get_method() == 'POST': return Request(newurl, headers=dict(req.header_items()), data=req.data, origin_req_host=req.get_origin_req_host(), unverifiable=True) else: return HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl) class App: @cherrypy.expose def index(self): opener = urllib2.build_opener(RedirectHandler()) return opener.open('http://localhost:8080/redirect', data='foo=bar') @cherrypy.expose def redirect(self, **kwargs): print('Before redirect {0}'.format(kwargs)) raise cherrypy.HTTPRedirect('/target', 302) @cherrypy.expose def target(self, **kwargs): return 'Target received {0} {1}'.format(cherrypy.request.method, kwargs) if __name__ == '__main__': cherrypy.quickstart(App(), '/', config)