Created
May 22, 2012 05:29
-
-
Save e000/2766826 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Cryto.net Tahoe-LAFS -> WSGI Gateway Proxy | |
-------------------- | |
Everyone is permitted to copy and distribute verbatim or modified | |
copies of this license document, and changing it is allowed as long | |
as the name is changed. | |
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
0. You just DO WHAT THE FUCK YOU WANT TO. | |
This wsgi application's entry point is `app`, you can run it with gunicorn: | |
gunicorn gateway:app [options] | |
Or you can run it stand-alone: | |
python gateway.py [options, --help] | |
""" | |
from wsgiref.util import is_hop_by_hop | |
import base64 | |
import socket | |
import urllib | |
import urllib2 | |
import logging | |
import time | |
SERVER_SOFTWARE = 'Cryto-Tahoe-Gateway 2.0' | |
try: | |
import gunicorn | |
gunicorn.SERVER_SOFTWARE = SERVER_SOFTWARE = SERVER_SOFTWARE + "/gunicorn" | |
del gunicorn | |
except ImportError: | |
pass | |
gatewayLog = logging.getLogger("gateway-wsgi") | |
_config_gatewayTimeout = 15 | |
_config_tahoeServer = 'localhost:3456' | |
_config_chunkSize = 1 << 13 # 8192, 8kb chunks. | |
_response_skeleton =\ | |
""" | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>%(page_title)s</title> | |
</head> | |
<body> | |
<h2>%(page_header)s</h2> | |
%(page_text)s<br><br> | |
<a href="http://cryto.net/">Learn more about the Cryto Coding Collective</a>. | |
<br><br><br><hr> | |
<div style="text-align: right; font-style: italic;">Cryto-Tahoe-Gateway v1.1</div> | |
</body> | |
</html> | |
""" | |
def error_404(environ, start_response): | |
""" | |
Returns a 404 page to the client. | |
""" | |
start_response("404 Not Found", [("Content-Type", "text/html"), ("Server", SERVER_SOFTWARE)]) | |
return [_response_skeleton % dict( | |
page_title = 'Cryto-Tahoe-Gateway: 404', | |
page_header = 'The specified resource was not found.', | |
page_text = 'The file may have expired, or the hyperlink you followed may have been broken.' | |
)] | |
def error_500(environ, start_response): | |
""" | |
Returns a 500 page to the client. | |
""" | |
start_response("500 Internal Server Error", [("Content-Type", "text/html"), ("Server", SERVER_SOFTWARE)]) | |
return [_response_skeleton % dict( | |
page_title = 'Cryto-Tahoe-Gateway: 500', | |
page_header = 'An error has occurred, and the gateway could not process your request, please try again.', | |
page_text = '' | |
)] | |
def error_50x(environ, start_response, code = '502', message = "Gateway Timeout"): | |
""" | |
Generic error page... can return any type of error, not only 502. | |
""" | |
start_response("%s %s" % (code, message), [("Content-Type", "text/html"), ("Server", SERVER_SOFTWARE)]) | |
return [_response_skeleton % dict( | |
page_title = 'Cryto-Tahoe-Gateway: %s' % code, | |
page_header = 'Gateway Error %s: %s'% (code, message), | |
page_text = '' | |
)] | |
def index(environ, start_response): | |
""" | |
Generic index. | |
""" | |
start_response("200 OK", [("Content-Type", "text/html"), ("Server", SERVER_SOFTWARE)]) | |
return [_response_skeleton % dict( | |
page_title = "Cryto-Tahoe-Gateway: Index", | |
page_header = "This gateway does not provide an index page.", | |
page_text = "Please use a direct URL to download a file hosted on this storage grid." | |
)] | |
def proxy_pass(environ, start_response): | |
""" | |
Proxy the request to tahoe. | |
""" | |
path = environ['PATH_INFO'] | |
pathParts = path.split('/') | |
if len(pathParts) != 4: | |
raise NotFoundError() | |
# Convert url to tahoe-type URL. | |
_, _, urlIdentifier, fileName = pathParts | |
urlIdentifier = urllib.quote(base64.urlsafe_b64decode(urlIdentifier)) | |
fileName = urllib.quote(fileName) | |
localUri = "http://%s/file/%s/@@named=/%s" % (_config_tahoeServer, urlIdentifier, fileName) | |
gatewayLog.debug("Proxy passing request (ident: %s, file: %s)", urlIdentifier, fileName) | |
# The actual proxying starts here. | |
try: | |
fp = urllib2.urlopen(localUri, timeout = _config_gatewayTimeout) | |
except urllib2.HTTPError, e: | |
# Eat any non 200 errors | |
gatewayLog.exception("HTTP Error") | |
if int(e.code) == 404: | |
raise NotFoundError() | |
else: | |
return error_50x(environ, start_response, e.code, e.msg) | |
except urllib2.URLError, e: | |
# Something went awry connecting to the backend. | |
gatewayLog.exception("Error connecting to backend...") | |
try: | |
if isinstance(e.args[0], Exception): | |
raise e.args[0] | |
else: | |
raise e | |
except socket.timeout: | |
return error_50x(environ, start_response, '504', "Gateway Timeout") | |
except Exception, e: | |
return error_50x(environ, start_response, '503', "Service Unavailable") | |
else: | |
# Do the actual proxying | |
data_sent = 0 | |
req_start = time.time() | |
headers = fp.info() | |
headers.dict.update({'cache-control': 'max-age=31536000', | |
'expires': 'Fri, 12 Dec 2020 05:00:00 GMT'}) | |
content_length = headers.getheader("Content-Length", 0) | |
try: | |
response_headers = [ | |
(('-'.join([x.capitalize() for x in k.split('-')])), v) | |
for k, v in headers.items() if not is_hop_by_hop(k) | |
] | |
write = start_response("200 OK", response_headers) | |
while True: | |
chunk = fp.read(_config_chunkSize) | |
if not chunk: | |
break | |
write(chunk) | |
data_sent += len(chunk) | |
gatewayLog.debug("Finished proxied request of %s, elapsed: %.02fs, transfer: %s bytes.", fileName, | |
time.time() - req_start, data_sent) | |
except Exception, e: | |
gatewayLog.exception("Error transfering proxied content... %s, sent %s of %s, elapsed %.02f", | |
fileName, data_sent, content_length, time.time() - req_start) | |
finally: | |
fp.close() | |
# Wsgi spec says we have to return an empty iterable ._. | |
return () | |
class NotFoundError(Exception): | |
pass | |
def app(environ, start_response): | |
""" | |
Application entry point, provide me to a wsgi handler! | |
""" | |
try: | |
path = environ['PATH_INFO'] | |
if not path or path == '/': | |
return index(environ, start_response) | |
elif path.startswith('/download/'): | |
return proxy_pass(environ, start_response) | |
else: | |
raise NotFoundError() | |
except NotFoundError: | |
return error_404(environ, start_response) | |
except Exception, e: | |
gatewayLog.exception("WSGI Application encountered error") | |
return error_500(environ, start_response) | |
def main(): | |
from optparse import OptionParser | |
parser = OptionParser() | |
parser.add_option("-i", '--interface', dest = "interface", help = "interface to bind on", default = "127.0.0.1") | |
parser.add_option("-p", '--listen-port', dest = "listen_port", help = "port to listen on", type="int", | |
default = 3719) | |
parser.add_option("-u", '--tahoe-url', dest = "tahoe_url", help = "address that tahoe is listening on, in form" | |
" host:port", default = 'localhost:3456') | |
parser.add_option("-c", '--proxy-chunk-size', dest = "chunk_size", help = "chunk size to read while proxying", | |
type = "int", default = 1 << 13) | |
parser.add_option("-t", '--gateway-timeout', dest = "gateway_timeout", help = "timeout while connecting to gateway", | |
type = "int", default = 15) | |
parser.add_option("-d", '--debug', dest = "debug", action = "store_true", default = False, | |
help = "debug logging level") | |
options, args = parser.parse_args() | |
global _config_chunkSize, _config_gatewayTimeout, _config_tahoeServer | |
_config_chunkSize = options.chunk_size | |
_config_gatewayTimeout = options.gateway_timeout | |
_config_tahoeServer = options.tahoe_url | |
if options.debug: | |
gatewayLog.setLevel(logging.DEBUG) | |
logging.basicConfig() | |
import wsgiref.simple_server | |
wsgiref.simple_server.make_server(options.interface, options.listen_port, app).serve_forever() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment