Skip to content

Commit

Permalink
fixed rocket (removed handling of static files, faster parse headers,…
Browse files Browse the repository at this point in the history
… pathoc fault-tolerant)
  • Loading branch information
mdipierro committed Oct 1, 2012
1 parent f70737d commit 12cc811
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 227 deletions.
2 changes: 1 addition & 1 deletion VERSION
@@ -1 +1 @@
Version 2.0.9 (2012-09-30 16:50:47) dev
Version 2.0.9 (2012-10-01 11:57:39) dev
250 changes: 24 additions & 226 deletions gluon/rocket.py
Expand Up @@ -2,6 +2,7 @@

# This file is part of the Rocket Web Server
# Copyright (c) 2011 Timothy Farrell
# Modified by Massimo Di Pierro

# Import System Modules
import sys
Expand All @@ -12,7 +13,7 @@
import traceback

# Define Constants
VERSION = '1.2.4'
VERSION = '1.2.5'
SERVER_NAME = socket.gethostname()
SERVER_SOFTWARE = 'Rocket %s' % VERSION
HTTP_SERVER_SOFTWARE = '%s Python/%s' % (SERVER_SOFTWARE, sys.version.split(' ')[0])
Expand Down Expand Up @@ -1454,37 +1455,34 @@ def _read_request_line_jython(self, d):
return req


def read_headers(self, sock_file):
def read_headers(self, sock_file, environ):
try:
headers = dict()
lname = lval = ''
lname = None
while True:
line = sock_file.readline()
l = sock_file.readline()
if PY3K:
try:
line = str(line, 'ISO-8859-1')
l = str(l, 'ISO-8859-1')
except UnicodeDecodeError:
self.err_log.warning('Client sent invalid header: ' + repr(line))
if line == '\r\n':
if lname: headers[str(lname)] = str(lval)
self.err_log.warning('Invalid request header: '+repr(l))

if l.strip() == '':
break
elif line.strip() == '' or '\0' in line:
raise BadRequest("Empty line in hader")
elif line[0] in ' \t' and lname:
elif l[0] in ' \t' and lname:
# Some headers take more than one line
lval += ' ' + line.strip()
elif ':' in line:
if lname: headers[str(lname)] = str(lval)
lname, lval = line.split(':', 1)
# HTTP header names are us-ascii encoded
lname = lname.strip().upper().replace('-', '_')
environ[lname] += ',' + l.strip()
else:
# HTTP header values are latin-1 encoded
lval = lval.strip()
l = l.split(':', 1)
# HTTP header names are us-ascii encoded

lname = str('HTTP_'+l[0].strip().upper().replace('-', '_'))
lval = str(l[-1].strip())
environ[lname] = lval

except socket.timeout:
raise SocketTimeout("Socket timed out before request.")

return headers

class SocketTimeout(Exception):
"Exception for when a socket times out between requests."
pass
Expand Down Expand Up @@ -1545,209 +1543,10 @@ def readlines(self):
yield self.readline()

def get_method(method):


methods = dict(wsgi=WSGIWorker,
fs=FileSystemWorker)
methods = dict(wsgi=WSGIWorker)
return methods[method.lower()]

# Monolithic build...end of module: rocket\worker.py
# Monolithic build...start of module: rocket\methods\__init__.py

# Monolithic build...end of module: rocket\methods\__init__.py
# Monolithic build...start of module: rocket\methods\fs.py

# Import System Modules
import os
import time
import mimetypes
from email.utils import formatdate
from wsgiref.headers import Headers
from wsgiref.util import FileWrapper
# Import Package Modules
# package imports removed in monolithic build


# Define Constants
CHUNK_SIZE = 2**16 # 64 Kilobyte chunks
HEADER_RESPONSE = '''HTTP/1.1 %s\r\n%s'''
INDEX_HEADER = '''\
<html>
<head><title>Directory Index: %(path)s</title>
<style> .parent { margin-bottom: 1em; }</style>
</head>
<body><h1>Directory Index: %(path)s</h1>
<table>
<tr><th>Directories</th></tr>
'''
INDEX_ROW = '''<tr><td><div class="%(cls)s"><a href="/%(link)s">%(name)s</a></div></td></tr>'''
INDEX_FOOTER = '''</table></body></html>\r\n'''

class LimitingFileWrapper(FileWrapper):
def __init__(self, limit=None, *args, **kwargs):
self.limit = limit
FileWrapper.__init__(self, *args, **kwargs)

def read(self, amt):
if amt > self.limit:
amt = self.limit
self.limit -= amt
return FileWrapper.read(self, amt)

class FileSystemWorker(Worker):
def __init__(self, *args, **kwargs):
"""Builds some instance variables that will last the life of the
thread."""

Worker.__init__(self, *args, **kwargs)

self.root = os.path.abspath(self.app_info['document_root'])
self.display_index = self.app_info['display_index']

def serve_file(self, filepath, headers):
filestat = os.stat(filepath)
self.size = filestat.st_size
modtime = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
time.gmtime(filestat.st_mtime))
self.headers.add_header('Last-Modified', modtime)
if headers.get('if_modified_since') == modtime:
# The browser cache is up-to-date, send a 304.
self.status = "304 Not Modified"
self.data = []
return

ct = mimetypes.guess_type(filepath)[0]
self.content_type = ct if ct else 'text/plain'
try:
f = open(filepath, 'rb')
self.headers['Pragma'] = 'cache'
self.headers['Cache-Control'] = 'private'
self.headers['Content-Length'] = str(self.size)
if self.etag:
self.headers.add_header('Etag', self.etag)
if self.expires:
self.headers.add_header('Expires', self.expires)

try:
# Implement 206 partial file support.
start, end = headers['range'].split('-')
start = 0 if not start.isdigit() else int(start)
end = self.size if not end.isdigit() else int(end)
if self.size < end or start < 0:
self.status = "214 Unsatisfiable Range Requested"
self.data = FileWrapper(f, CHUNK_SIZE)
else:
f.seek(start)
self.data = LimitingFileWrapper(f, CHUNK_SIZE, limit=end)
self.status = "206 Partial Content"
except:
self.data = FileWrapper(f, CHUNK_SIZE)
except IOError:
self.status = "403 Forbidden"

def serve_dir(self, pth, rpth):
def rel_path(path):
return os.path.normpath(path[len(self.root):] if path.startswith(self.root) else path)

if not self.display_index:
self.status = '404 File Not Found'
return b('')
else:
self.content_type = 'text/html'

dir_contents = [os.path.join(pth, x) for x in os.listdir(os.path.normpath(pth))]
dir_contents.sort()

dirs = [rel_path(x)+'/' for x in dir_contents if os.path.isdir(x)]
files = [rel_path(x) for x in dir_contents if os.path.isfile(x)]

self.data = [INDEX_HEADER % dict(path='/'+rpth)]
if rpth:
self.data += [INDEX_ROW % dict(name='(parent directory)', cls='dir parent', link='/'.join(rpth[:-1].split('/')[:-1]))]
self.data += [INDEX_ROW % dict(name=os.path.basename(x[:-1]), link=os.path.join(rpth, os.path.basename(x[:-1])).replace('\\', '/'), cls='dir') for x in dirs]
self.data += ['<tr><th>Files</th></tr>']
self.data += [INDEX_ROW % dict(name=os.path.basename(x), link=os.path.join(rpth, os.path.basename(x)).replace('\\', '/'), cls='file') for x in files]
self.data += [INDEX_FOOTER]
self.headers['Content-Length'] = self.size = str(sum([len(x) for x in self.data]))
self.status = '200 OK'

def run_app(self, conn):
self.status = "200 OK"
self.size = 0
self.expires = None
self.etag = None
self.content_type = 'text/plain'
self.content_length = None

if __debug__:
self.err_log.debug('Getting sock_file')

# Build our file-like object
sock_file = conn.makefile('rb',BUF_SIZE)
request = self.read_request_line(sock_file)
if request['method'].upper() not in ('GET', ):
self.status = "501 Not Implemented"

try:
# Get our file path
reader = self.read_headers(sock_file)
headers = dict((k.lower(),v) for k,v in reader.iteritems())
rpath = request.get('path', '').lstrip('/')
filepath = os.path.join(self.root, rpath)
filepath = os.path.abspath(filepath)
if __debug__:
self.err_log.debug('Request for path: %s' % filepath)

self.closeConnection = headers.get('connection', 'close').lower() == 'close'
self.headers = Headers([('Date', formatdate(usegmt=True)),
('Server', HTTP_SERVER_SOFTWARE),
('Connection', headers.get('connection', 'close')),
])

if not filepath.lower().startswith(self.root.lower()):
# File must be within our root directory
self.status = "400 Bad Request"
self.closeConnection = True
elif not os.path.exists(filepath):
self.status = "404 File Not Found"
self.closeConnection = True
elif os.path.isdir(filepath):
self.serve_dir(filepath, rpath)
elif os.path.isfile(filepath):
self.serve_file(filepath, headers)
else:
# It exists but it's not a file or a directory????
# What is it then?
self.status = "501 Not Implemented"
self.closeConnection = True

h = self.headers
statcode, statstr = self.status.split(' ', 1)
statcode = int(statcode)
if statcode >= 400:
h.add_header('Content-Type', self.content_type)
self.data = [statstr]

# Build our output headers
header_data = HEADER_RESPONSE % (self.status, str(h))

# Send the headers
if __debug__:
self.err_log.debug('Sending Headers: %s' % repr(header_data))
self.conn.sendall(b(header_data))

for data in self.data:
self.conn.sendall(b(data))

if hasattr(self.data, 'close'):
self.data.close()

finally:
if __debug__:
self.err_log.debug('Finally closing sock_file')
sock_file.close()

# Monolithic build...end of module: rocket\methods\fs.py
# Monolithic build...start of module: rocket\methods\wsgi.py

# Import System Modules
Expand Down Expand Up @@ -1815,16 +1614,15 @@ def build_environ(self, sock_file, conn):
environ = self.base_environ.copy()

# Grab the headers
for k, v in self.read_headers(sock_file).items():
environ[str('HTTP_'+k)] = v
self.read_headers(sock_file,environ)

# Add CGI Variables
environ['REQUEST_METHOD'] = request['method']
environ['PATH_INFO'] = request['path']
environ['SERVER_PROTOCOL'] = request['protocol']
environ['SERVER_PORT'] = str(conn.server_port)
environ['REMOTE_PORT'] = str(conn.client_port)
environ['REMOTE_ADDR'] = str(conn.client_addr)
environ['REQUEST_METHOD'] = request['method']
environ['PATH_INFO'] = request['path']
environ['SERVER_PROTOCOL'] = request['protocol']
environ['QUERY_STRING'] = request['query_string']
if 'HTTP_CONTENT_LENGTH' in environ:
environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH']
Expand Down

0 comments on commit 12cc811

Please sign in to comment.