New gateway script

master
Sven Slootweg 12 years ago
parent 444f294bea
commit 235bf849fe

@ -1,118 +0,0 @@
from twisted.internet import protocol, reactor
from twisted.web import static, server, proxy
from twisted.web.resource import Resource
import base64, urllib
from os import fork, setsid, umask, dup2
from sys import stdin, stdout, stderr
from os import getpid
from urllib import quote as urlquote
pid_file = "/home/tahoe/gateway/gateway.pid"
modHeaders = {
'Cache-Control': 'max-age=31536000',
'Server': 'Cryto-Tahoe-Gateway 1.1',
'Expires': 'Fri, 12 Dec 2012 05:00:00 GMT'
}
outfile = open(pid_file, 'w')
outfile.write('%i' % getpid())
outfile.close()
if fork(): exit(0)
umask(0)
setsid()
if fork(): exit(0)
stdout.flush()
stderr.flush()
si = file('/dev/null', 'r')
so = file('/dev/null', 'a+')
se = file('/dev/null', 'a+', 0)
dup2(si.fileno(), stdin.fileno())
dup2(so.fileno(), stdout.fileno())
dup2(se.fileno(), stderr.fileno())
error_index = ("""\
<!DOCTYPE html>
<html>
<head>
<title>Cryto-Tahoe-Gateway: Index</title>
</head>
<body>
<h2>This gateway does not provide an index page.</h2>
Please use a direct URL to download a file hosted on this storage grid.<br><br>
Alternatively, <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>""")
error_404 = ("""\
<!DOCTYPE html>
<html>
<head>
<title>Cryto-Tahoe-Gateway: 404</title>
</head>
<body>
<h2>The specified resource was not found.</h2>
The file may have expired, or the hyperlink you followed may have been broken.<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>""")
class ProxyClient(proxy.ProxyClient):
"""A proxy class that injects headers to the response."""
def handleEndHeaders(self):
for key, value in modHeaders.iteritems():
self.father.responseHeaders.setRawHeaders(key, [value])
class ProxyClientFactory(proxy.ProxyClientFactory):
protocol = ProxyClient
class ReverseProxyResource(proxy.ReverseProxyResource):
proxyClientFactoryClass = ProxyClientFactory
def getChild(self, path, request):
return ReverseProxyResource(
self.host, self.port, self.path + '/' + urlquote(path, safe=""))
class GatewayResource(Resource):
isLeaf = False
allowedMethods = ("GET")
def getChild(self, name, request):
if name == "download":
try:
uri = request.path
uriParts = uri.split('/')
uriIdentifier = base64.urlsafe_b64decode(uriParts[2])
fileName = uriParts[3]
localUri = "/file/" + urllib.quote(uriIdentifier) + "/@@named=/" + urllib.quote(fileName)
return ReverseProxyResource('localhost', 3456, localUri)
except:
return self
else:
return self
def render_GET(self, request):
path = request.path
if path == "/":
return error_index
else:
return error_404
resource = GatewayResource()
site = server.Site(resource)
reactor.listenTCP(3719, site)
reactor.run()

@ -0,0 +1,228 @@
"""
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
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")])
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")])
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")])
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")])
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()
content_length = fp.info().getheader("Content-Length", 0)
try:
response_headers = [(k, v) for k, v in fp.info().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()

@ -0,0 +1,74 @@
#! /bin/bash
### BEGIN INIT INFO
# Provides: Tahoe-LAFS Gateway
### END INIT INFO
#### SERVER SPECIFIC CONFIGURATION
GATEWAY_PATH="/home/tahoe/gateway/run"
PID_PATH="/home/tahoe/gateway/gateway.pid"
RUN_AS=tahoe
#### DO NOT CHANGE ANYTHING AFTER THIS LINE!
set -e
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="Tahoe-LAFS Gateway"
NAME=$0
SCRIPTNAME=/etc/init.d/$NAME
#
# Function that starts the daemon/service.
#
d_start()
{
# Starting Tahoe-LAFS Gateway
if [ -f $PID_PATH ]; then
echo -n ", already running"
else
start-stop-daemon --start --quiet --pidfile $PID_PATH \
--chuid $RUN_AS --exec /usr/bin/env -- $GATEWAY_PATH
chmod 400 $PID_PATH
fi
}
#
# Function that stops the daemon/service.
#
d_stop() {
# Killing the Tahoe-LAFS Gateway
start-stop-daemon --stop --quiet --pidfile $PID_PATH \
|| echo -n ", not running"
if [ -f $PID_PATH ]; then
rm $PID_PATH
fi
}
ACTION="$1"
case "$ACTION" in
start)
echo -n "Starting $DESC: $NAME"
d_start
echo "."
;;
stop)
echo -n "Stopping $DESC: $NAME"
d_stop
echo "."
;;
restart|force-reload)
echo -n "Restarting $DESC: $NAME"
d_stop
sleep 1
d_start
echo "."
;;
*)
echo "Usage: $NAME {start|stop|restart|force-reload}" >&2
exit 3
;;
esac
exit 0

@ -0,0 +1,4 @@
#!/bin/bash
cd /home/tahoe/gateway
gunicorn -b 0.0.0.0:3719 -k gevent gateway_wsgi:app >/dev/null 2>/dev/null &
echo $! > gateway.pid
Loading…
Cancel
Save