Skip to content

Instantly share code, notes, and snippets.

@blackandred
Created May 31, 2018 19:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blackandred/049cafc5a8cbf620bae41309e85cd026 to your computer and use it in GitHub Desktop.
Save blackandred/049cafc5a8cbf620bae41309e85cd026 to your computer and use it in GitHub Desktop.
Tornado: Redirect all traffic to external server

Redirects all traffic to other server/application if route was not matched (acts as a 404), but can be configured to be only on specific route. Strongly based on https://github.com/senko/tornado-proxy

app = Application([
# ...
# some routes
# ...
# act as a fallback, redirect all not matched routes to a second server
(r"/.*", ProxyController, {'domain': 'your-host.org:8001', 'scheme': 'https'})
])
from tornado import web
import tornado
import tornado.httpclient
import tornado.httputil
import logging
from .BaseController import BaseHandler
logger = logging.getLogger('tornado_proxy')
def fetch_request(url, callback, **kwargs):
req = tornado.httpclient.HTTPRequest(url, **kwargs)
client = tornado.httpclient.AsyncHTTPClient()
client.fetch(req, callback, raise_error=False)
class ProxyController(BaseHandler):
SUPPORTED_METHODS = ['GET', 'POST', 'PUT', 'DELETE']
domain = ""
scheme = ""
def __init__(self, application, request, domain: str, scheme: str):
web.RequestHandler.__init__(self, application=application, request=request)
self.domain = domain
self.scheme = scheme
def data_received(self, chunk):
pass
def compute_etag(self):
return None
@tornado.web.asynchronous
def get(self):
""" Handles a request and forwards to external server """
self.request.uri = self.scheme + '://' + self.domain + self.request.uri
self.request.host = self.domain
self.request.headers['Host'] = self.domain
logger.debug('Handle %s request to %s', self.request.method,
self.request.uri)
def handle_response(response):
if (response.error and not
isinstance(response.error, tornado.httpclient.HTTPError)):
self.set_status(500)
self.write('Internal server error:\n' + str(response.error))
else:
self.set_status(response.code, response.reason)
self._headers = tornado.httputil.HTTPHeaders() # clear tornado default header
for header, v in response.headers.get_all():
if header not in ('Content-Length', 'Transfer-Encoding', 'Content-Encoding', 'Connection'):
self.add_header(header, v) # some header appear multiple times, eg 'Set-Cookie'
if response.body:
self.set_header('Content-Length', len(response.body))
self.write(response.body)
self.finish()
body = self.request.body
if not body:
body = None
try:
fetch_request(
self.request.uri, handle_response,
method=self.request.method, body=body,
headers=self.request.headers, follow_redirects=False,
allow_nonstandard_methods=True)
except tornado.httpclient.HTTPError as e:
if hasattr(e, 'response') and e.response:
handle_response(e.response)
else:
print(e)
self.set_status(500)
self.write('Internal server error:\n' + str(e))
self.finish()
@tornado.web.asynchronous
def post(self):
""" Post is handled same as GET method """
return self.get()
@tornado.web.asynchronous
def put(self, *args, **kwargs):
return self.post()
@tornado.web.asynchronous
def delete(self, *args, **kwargs):
return self.delete()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment