diff --git a/gateway.py b/gateway.py
deleted file mode 100644
index 246d099..0000000
--- a/gateway.py
+++ /dev/null
@@ -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 = ("""\
-
-
-
- Cryto-Tahoe-Gateway: Index
-
-
- This gateway does not provide an index page.
- Please use a direct URL to download a file hosted on this storage grid.
- Alternatively, learn more about the Cryto Coding Collective.
-
- Cryto-Tahoe-Gateway v1.1
-
- """)
-
-error_404 = ("""\
-
-
-
- Cryto-Tahoe-Gateway: 404
-
-
- The specified resource was not found.
- The file may have expired, or the hyperlink you followed may have been broken.
- Learn more about the Cryto Coding Collective.
-
- Cryto-Tahoe-Gateway v1.1
-
- """)
-
-
-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()
-
-
diff --git a/gateway/gateway_wsgi.py b/gateway/gateway_wsgi.py
new file mode 100644
index 0000000..3c59435
--- /dev/null
+++ b/gateway/gateway_wsgi.py
@@ -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 =\
+"""
+
+
+
+ %(page_title)s
+
+
+ %(page_header)s
+ %(page_text)s
+ Learn more about the Cryto Coding Collective.
+
+ Cryto-Tahoe-Gateway v1.1
+
+
+"""
+
+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()
diff --git a/gateway/initscript/tahoegateway b/gateway/initscript/tahoegateway
new file mode 100644
index 0000000..b212f43
--- /dev/null
+++ b/gateway/initscript/tahoegateway
@@ -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
diff --git a/gateway/run b/gateway/run
new file mode 100644
index 0000000..3126965
--- /dev/null
+++ b/gateway/run
@@ -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