Merge pull request #44 from cwinebrinner/rocket_ipv6_and_cleanup

explorigin/Rocket merged and IPv6 support added
This commit is contained in:
mdipierro
2012-11-24 08:06:54 -08:00
5 changed files with 268 additions and 250 deletions
+16 -11
View File
@@ -127,7 +127,7 @@ def get_client(env):
guess the client address from the environment variables guess the client address from the environment variables
first tries 'http_x_forwarded_for', secondly 'remote_addr' first tries 'http_x_forwarded_for', secondly 'remote_addr'
if all fails assume '127.0.0.1' (running locally) if all fails, assume '127.0.0.1' or '::1' (running locally)
""" """
g = regex_client.search(env.get('http_x_forwarded_for', '')) g = regex_client.search(env.get('http_x_forwarded_for', ''))
if g: if g:
@@ -136,8 +136,10 @@ def get_client(env):
g = regex_client.search(env.get('remote_addr', '')) g = regex_client.search(env.get('remote_addr', ''))
if g: if g:
client = g.group() client = g.group()
elif env.http_host.startswith('['): # IPv6
client = '::1'
else: else:
client = '127.0.0.1' client = '127.0.0.1' # IPv4
if not is_valid_ip_address(client): if not is_valid_ip_address(client):
raise HTTP(400, "Bad Request (request.client=%s)" % client) raise HTTP(400, "Bad Request (request.client=%s)" % client)
return client return client
@@ -432,20 +434,23 @@ def wsgibase(environ, responder):
app = request.application # must go after url_in! app = request.application # must go after url_in!
if not global_settings.local_hosts: if not global_settings.local_hosts:
local_hosts = ['127.0.0.1', '::ffff:127.0.0.1'] local_hosts = set(['127.0.0.1', '::ffff:127.0.0.1', '::1'])
if not global_settings.web2py_runtime_gae: if not global_settings.web2py_runtime_gae:
try: try:
local_hosts.append(socket.gethostname()) fqdn = socket.getfqdn()
except TypeError: local_hosts.add(socket.gethostname())
pass local_hosts.add(fqdn)
try: local_hosts.update([
ip[4][0] for ip in socket.getaddrinfo(
fqdn, 0)])
if env.server_name: if env.server_name:
local_hosts += [ local_hosts.add(env.server_name)
env.server_name, local_hosts.update([
socket.gethostbyname(env.server_name)] ip[4][0] for ip in socket.getaddrinfo(
env.server_name, 0)])
except (socket.gaierror, TypeError): except (socket.gaierror, TypeError):
pass pass
global_settings.local_hosts = local_hosts global_settings.local_hosts = list(local_hosts)
else: else:
local_hosts = global_settings.local_hosts local_hosts = global_settings.local_hosts
client = get_client(env) client = get_client(env)
+127 -163
View File
@@ -10,10 +10,9 @@ import errno
import socket import socket
import logging import logging
import platform import platform
import traceback
# Define Constants # Define Constants
VERSION = '1.2.5' VERSION = '1.2.6'
SERVER_NAME = socket.gethostname() SERVER_NAME = socket.gethostname()
SERVER_SOFTWARE = 'Rocket %s' % VERSION SERVER_SOFTWARE = 'Rocket %s' % VERSION
HTTP_SERVER_SOFTWARE = '%s Python/%s' % ( HTTP_SERVER_SOFTWARE = '%s Python/%s' % (
@@ -79,8 +78,8 @@ __all__ = ['VERSION', 'SERVER_SOFTWARE', 'HTTP_SERVER_SOFTWARE', 'BUF_SIZE',
'IS_JYTHON', 'IGNORE_ERRORS_ON_CLOSE', 'DEFAULTS', 'PY3K', 'b', 'u', 'IS_JYTHON', 'IGNORE_ERRORS_ON_CLOSE', 'DEFAULTS', 'PY3K', 'b', 'u',
'Rocket', 'CherryPyWSGIServer', 'SERVER_NAME', 'NullHandler'] 'Rocket', 'CherryPyWSGIServer', 'SERVER_NAME', 'NullHandler']
# Monolithic build...end of module: rocket\__init__.py # Monolithic build...end of module: rocket/__init__.py
# Monolithic build...start of module: rocket\connection.py # Monolithic build...start of module: rocket/connection.py
# Import System Modules # Import System Modules
import sys import sys
@@ -118,7 +117,7 @@ class Connection(object):
] ]
def __init__(self, sock_tuple, port, secure=False): def __init__(self, sock_tuple, port, secure=False):
self.client_addr, self.client_port = sock_tuple[1] self.client_addr, self.client_port = sock_tuple[1][:2]
self.server_port = port self.server_port = port
self.socket = sock_tuple[0] self.socket = sock_tuple[0]
self.start_time = time.time() self.start_time = time.time()
@@ -133,7 +132,6 @@ class Connection(object):
self.socket.settimeout(SOCKET_TIMEOUT) self.socket.settimeout(SOCKET_TIMEOUT)
self.sendall = self.socket.sendall
self.shutdown = self.socket.shutdown self.shutdown = self.socket.shutdown
self.fileno = self.socket.fileno self.fileno = self.socket.fileno
self.setblocking = self.socket.setblocking self.setblocking = self.socket.setblocking
@@ -141,6 +139,26 @@ class Connection(object):
self.send = self.socket.send self.send = self.socket.send
self.makefile = self.socket.makefile self.makefile = self.socket.makefile
if sys.platform == 'darwin':
self.sendall = self._sendall_darwin
else:
self.sendall = self.socket.sendall
def _sendall_darwin(self, buf):
pending = len(buf)
offset = 0
while pending:
try:
sent = self.socket.send(buf[offset:])
pending -= sent
offset += sent
except socket.error:
import errno
info = sys.exc_info()
if info[1].args[0] != errno.EAGAIN:
raise
return offset
# FIXME - this is not ready for prime-time yet. # FIXME - this is not ready for prime-time yet.
# def makefile(self, buf_size=BUF_SIZE): # def makefile(self, buf_size=BUF_SIZE):
# return FileLikeSocket(self, buf_size) # return FileLikeSocket(self, buf_size)
@@ -157,9 +175,8 @@ class Connection(object):
pass pass
self.socket.close() self.socket.close()
# Monolithic build...end of module: rocket/connection.py
# Monolithic build...end of module: rocket\connection.py # Monolithic build...start of module: rocket/filelike.py
# Monolithic build...start of module: rocket\filelike.py
# Import System Modules # Import System Modules
import socket import socket
@@ -282,8 +299,8 @@ class FileLikeSocket(object):
self.conn = None self.conn = None
self.content_length = None self.content_length = None
# Monolithic build...end of module: rocket\filelike.py # Monolithic build...end of module: rocket/filelike.py
# Monolithic build...start of module: rocket\futures.py # Monolithic build...start of module: rocket/futures.py
# Import System Modules # Import System Modules
import time import time
@@ -396,8 +413,8 @@ class FuturesMiddleware(object):
environ["wsgiorg.futures"] = self.executor.futures environ["wsgiorg.futures"] = self.executor.futures
return self.app(environ, start_response) return self.app(environ, start_response)
# Monolithic build...end of module: rocket\futures.py # Monolithic build...end of module: rocket/futures.py
# Monolithic build...start of module: rocket\listener.py # Monolithic build...start of module: rocket/listener.py
# Import System Modules # Import System Modules
import os import os
@@ -442,7 +459,10 @@ class Listener(Thread):
self.err_log.addHandler(NullHandler()) self.err_log.addHandler(NullHandler())
# Build the socket # Build the socket
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if ':' in self.addr:
listener = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else:
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if not listener: if not listener:
self.err_log.error("Failed to get socket.") self.err_log.error("Failed to get socket.")
@@ -520,12 +540,10 @@ class Listener(Thread):
certfile=self.interface[3], certfile=self.interface[3],
server_side=True, server_side=True,
ssl_version=ssl.PROTOCOL_SSLv23) ssl_version=ssl.PROTOCOL_SSLv23)
except SSLError: except SSLError:
# Generally this happens when an HTTP request is received on a # Generally this happens when an HTTP request is received on a
# secure socket. We don't do anything because it will be detected # secure socket. We don't do anything because it will be detected
# by Worker and dealt with appropriately. # by Worker and dealt with appropriately.
# self.err_log.error('SSL Error: %s' % traceback.format_exc())
pass pass
return sock return sock
@@ -576,8 +594,9 @@ class Listener(Thread):
self.secure)) self.secure))
except socket.timeout: except socket.timeout:
# socket.timeout will be raised every THREAD_STOP_CHECK_INTERVAL # socket.timeout will be raised every
# seconds. When that happens, we check if it's time to die. # THREAD_STOP_CHECK_INTERVAL seconds. When that happens,
# we check if it's time to die.
if not self.ready: if not self.ready:
if __debug__: if __debug__:
@@ -588,8 +607,8 @@ class Listener(Thread):
except: except:
self.err_log.error(traceback.format_exc()) self.err_log.error(traceback.format_exc())
# Monolithic build...end of module: rocket\listener.py # Monolithic build...end of module: rocket/listener.py
# Monolithic build...start of module: rocket\main.py # Monolithic build...start of module: rocket/main.py
# Import System Modules # Import System Modules
import sys import sys
@@ -606,7 +625,6 @@ except ImportError:
# Import Package Modules # Import Package Modules
# package imports removed in monolithic build # package imports removed in monolithic build
# Setup Logging # Setup Logging
log = logging.getLogger('Rocket') log = logging.getLogger('Rocket')
log.addHandler(NullHandler()) log.addHandler(NullHandler())
@@ -618,13 +636,13 @@ class Rocket(object):
def __init__(self, def __init__(self,
interfaces=('127.0.0.1', 8000), interfaces=('127.0.0.1', 8000),
method = 'wsgi', method='wsgi',
app_info = None, app_info=None,
min_threads = None, min_threads=None,
max_threads = None, max_threads=None,
queue_size = None, queue_size=None,
timeout = 600, timeout=600,
handle_signals = True): handle_signals=True):
self.handle_signals = handle_signals self.handle_signals = handle_signals
self.startstop_lock = Lock() self.startstop_lock = Lock()
@@ -801,8 +819,8 @@ def CherryPyWSGIServer(bind_addr,
queue_size=request_queue_size, queue_size=request_queue_size,
timeout=timeout) timeout=timeout)
# Monolithic build...end of module: rocket\main.py # Monolithic build...end of module: rocket/main.py
# Monolithic build...start of module: rocket\monitor.py # Monolithic build...start of module: rocket/monitor.py
# Import System Modules # Import System Modules
import time import time
@@ -983,8 +1001,8 @@ class Monitor(Thread):
# Place a None sentry value to cause the monitor to die. # Place a None sentry value to cause the monitor to die.
self.monitor_queue.put(None) self.monitor_queue.put(None)
# Monolithic build...end of module: rocket\monitor.py # Monolithic build...end of module: rocket/monitor.py
# Monolithic build...start of module: rocket\threadpool.py # Monolithic build...start of module: rocket/threadpool.py
# Import System Modules # Import System Modules
import logging import logging
@@ -1146,8 +1164,8 @@ class ThreadPool:
self.grow(queueSize) self.grow(queueSize)
# Monolithic build...end of module: rocket\threadpool.py # Monolithic build...end of module: rocket/threadpool.py
# Monolithic build...start of module: rocket\worker.py # Monolithic build...start of module: rocket/worker.py
# Import System Modules # Import System Modules
import re import re
@@ -1199,7 +1217,7 @@ $
""", re.X) """, re.X)
LOG_LINE = '%(client_ip)s - "%(request_line)s" - %(status)s %(size)s' LOG_LINE = '%(client_ip)s - "%(request_line)s" - %(status)s %(size)s'
RESPONSE = '''\ RESPONSE = '''\
HTTP/1.1 %s %s %s
Content-Length: %i Content-Length: %i
Content-Type: %s Content-Type: %s
@@ -1207,7 +1225,7 @@ Content-Type: %s
''' '''
if IS_JYTHON: if IS_JYTHON:
HTTP_METHODS = set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', HTTP_METHODS = set(['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT',
'DELETE', 'TRACE', 'CONNECT']) 'DELETE', 'TRACE', 'CONNECT'])
class Worker(Thread): class Worker(Thread):
@@ -1232,6 +1250,7 @@ class Worker(Thread):
self.status = "200 OK" self.status = "200 OK"
self.closeConnection = True self.closeConnection = True
self.request_line = "" self.request_line = ""
self.protocol = 'HTTP/1.1'
# Request Log # Request Log
self.req_log = logging.getLogger('Rocket.Requests') self.req_log = logging.getLogger('Rocket.Requests')
@@ -1316,26 +1335,19 @@ class Worker(Thread):
self.err_log.debug('Serving a request') self.err_log.debug('Serving a request')
try: try:
self.run_app(conn) self.run_app(conn)
log_info = dict(client_ip=conn.client_addr,
time=datetime.now().strftime('%c'),
status=self.status.split(' ')[0],
size=self.size,
request_line=self.request_line)
self.req_log.info(LOG_LINE % log_info)
except: except:
exc = sys.exc_info() exc = sys.exc_info()
handled = self._handleError(*exc) handled = self._handleError(*exc)
if handled: if handled:
break break
else: finally:
if self.request_line: if self.request_line:
log_info = dict(client_ip=conn.client_addr, log_info = dict(client_ip=conn.client_addr,
time=datetime.now( time=datetime.now().strftime('%c'),
).strftime('%c'), status=self.status.split(' ')[0],
status=self.status.split(' ')[0], size=self.size,
size=self.size, request_line=self.request_line)
request_line=self.request_line + ' - not stopping') self.req_log.info(LOG_LINE % log_info)
self.req_log.info(LOG_LINE % log_info)
if self.closeConnection: if self.closeConnection:
try: try:
@@ -1353,7 +1365,8 @@ class Worker(Thread):
def send_response(self, status): def send_response(self, status):
stat_msg = status.split(' ', 1)[1] stat_msg = status.split(' ', 1)[1]
msg = RESPONSE % (status, msg = RESPONSE % (self.protocol,
status,
len(stat_msg), len(stat_msg),
'text/plain', 'text/plain',
stat_msg) stat_msg)
@@ -1361,23 +1374,12 @@ class Worker(Thread):
self.conn.sendall(b(msg)) self.conn.sendall(b(msg))
except socket.timeout: except socket.timeout:
self.closeConnection = True self.closeConnection = True
self.err_log.error( msg = 'Tried to send "%s" to client but received timeout error'
'Tried to send "%s" to client but received timeout error' self.err_log.error(msg % status)
% status)
except socket.error: except socket.error:
self.closeConnection = True self.closeConnection = True
self.err_log.error( msg = 'Tried to send "%s" to client but received socket error'
'Tried to send "%s" to client but received socket error' self.err_log.error(msg % status)
% status)
#def kill(self):
# if self.isAlive() and hasattr(self, 'conn'):
# try:
# self.conn.shutdown(socket.SHUT_RDWR)
# except socket.error:
# info = sys.exc_info()
# if info[1].args[0] != socket.EBADF:
# self.err_log.debug('Error on shutdown: '+str(info))
def read_request_line(self, sock_file): def read_request_line(self, sock_file):
self.request_line = '' self.request_line = ''
@@ -1396,10 +1398,11 @@ class Worker(Thread):
if PY3K: if PY3K:
d = d.decode('ISO-8859-1') d = d.decode('ISO-8859-1')
except socket.timeout: except socket.timeout:
raise SocketTimeout("Socket timed out before request.") raise SocketTimeout('Socket timed out before request.')
except TypeError: except TypeError:
raise SocketClosed( raise SocketClosed(
"ssl bug caused closer of socket, upgrade to python 2.7") 'SSL bug caused closure of socket. See '
'"https://groups.google.com/d/topic/web2py/P_Gw0JxWzCs".')
d = d.strip() d = d.strip()
@@ -1433,6 +1436,7 @@ class Worker(Thread):
req['path'] = r'%2F'.join( req['path'] = r'%2F'.join(
[unquote(x) for x in re_SLASH.split(v)]) [unquote(x) for x in re_SLASH.split(v)])
self.protocol = req['protocol']
return req return req
def _read_request_line_jython(self, d): def _read_request_line_jython(self, d):
@@ -1440,7 +1444,7 @@ class Worker(Thread):
try: try:
method, uri, proto = d.split(' ') method, uri, proto = d.split(' ')
if not proto.startswith('HTTP') or \ if not proto.startswith('HTTP') or \
proto[-3:] not in ('1.0', '1.1') or \ proto[-3:] not in ('1.0', '1.1') or \
method not in HTTP_METHODS: method not in HTTP_METHODS:
self.send_response('400 Bad Request') self.send_response('400 Bad Request')
raise BadRequest raise BadRequest
@@ -1473,36 +1477,42 @@ class Worker(Thread):
host=host) host=host)
return req return req
def read_headers(self, sock_file, environ): def read_headers(self, sock_file):
try: try:
headers = dict()
lname = None lname = None
lval = None
while True: while True:
l = sock_file.readline() l = sock_file.readline()
if PY3K: if PY3K:
try: try:
l = str(l, 'ISO-8859-1') l = str(l, 'ISO-8859-1')
except UnicodeDecodeError: except UnicodeDecodeError:
self.err_log.warning( self.err_log.warning(
'Invalid request header: ' + repr(l)) 'Client sent invalid header: ' + repr(l))
if l.strip().replace('\0', '') == '': if l.strip().replace('\0', '') == '':
break break
elif l[0] in ' \t' and lname:
if l[0] in ' \t' and lname:
# Some headers take more than one line # Some headers take more than one line
environ[lname] += ' ' + l.strip() lval += ' ' + l.strip()
else: else:
# HTTP header values are latin-1 encoded # HTTP header values are latin-1 encoded
l = l.split(':', 1) l = l.split(':', 1)
# HTTP header names are us-ascii encoded # HTTP header names are us-ascii encoded
lname = str( lname = l[0].strip().upper().replace('-', '_')
'HTTP_' + l[0].strip().upper().replace('-', '_')) lval = l[-1].strip()
lval = str(l[-1].strip())
environ[lname] = lval headers[str(lname)] = str(lval)
except socket.timeout: except socket.timeout:
raise SocketTimeout("Socket timed out before request.") raise SocketTimeout("Socket timed out before request.")
return headers
class SocketTimeout(Exception): class SocketTimeout(Exception):
"Exception for when a socket times out between requests." "Exception for when a socket times out between requests."
@@ -1520,6 +1530,7 @@ class SocketClosed(Exception):
class ChunkedReader(object): class ChunkedReader(object):
def __init__(self, sock_file): def __init__(self, sock_file):
self.stream = sock_file self.stream = sock_file
self.chunk_size = 0 self.chunk_size = 0
@@ -1571,8 +1582,11 @@ def get_method(method):
methods = dict(wsgi=WSGIWorker) methods = dict(wsgi=WSGIWorker)
return methods[method.lower()] return methods[method.lower()]
# Monolithic build...end of module: rocket\worker.py # Monolithic build...end of module: rocket/worker.py
# Monolithic build...start of module: rocket\methods\wsgi.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/wsgi.py
# Import System Modules # Import System Modules
import sys import sys
@@ -1583,7 +1597,6 @@ from wsgiref.util import FileWrapper
# Import Package Modules # Import Package Modules
# package imports removed in monolithic build # package imports removed in monolithic build
if PY3K: if PY3K:
from email.utils import formatdate from email.utils import formatdate
else: else:
@@ -1640,15 +1653,16 @@ class WSGIWorker(Worker):
environ = self.base_environ.copy() environ = self.base_environ.copy()
# Grab the headers # Grab the headers
self.read_headers(sock_file, environ) for k, v in self.read_headers(sock_file).iteritems():
environ[str('HTTP_' + k)] = v
# Add CGI Variables # Add CGI Variables
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['REQUEST_METHOD'] = request['method']
environ['PATH_INFO'] = request['path'] environ['PATH_INFO'] = request['path']
environ['SERVER_PROTOCOL'] = request['protocol'] 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['QUERY_STRING'] = request['query_string'] environ['QUERY_STRING'] = request['query_string']
if 'HTTP_CONTENT_LENGTH' in environ: if 'HTTP_CONTENT_LENGTH' in environ:
environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH'] environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH']
@@ -1662,16 +1676,14 @@ class WSGIWorker(Worker):
if conn.ssl: if conn.ssl:
environ['wsgi.url_scheme'] = 'https' environ['wsgi.url_scheme'] = 'https'
environ['HTTPS'] = 'on' environ['HTTPS'] = 'on'
else:
environ['wsgi.url_scheme'] = 'http'
if conn.ssl:
try: try:
peercert = conn.socket.getpeercert(binary_form=True) peercert = conn.socket.getpeercert(binary_form=True)
environ['SSL_CLIENT_RAW_CERT'] = \ environ['SSL_CLIENT_RAW_CERT'] = \
peercert and ssl.DER_cert_to_PEM_cert(peercert) peercert and ssl.DER_cert_to_PEM_cert(peercert)
except Exception: except Exception:
print sys.exc_info()[1] print sys.exc_info()[1]
else:
environ['wsgi.url_scheme'] = 'http'
if environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked': if environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked':
environ['wsgi.input'] = ChunkedReader(sock_file) environ['wsgi.input'] = ChunkedReader(sock_file)
@@ -1684,36 +1696,36 @@ class WSGIWorker(Worker):
h_set = self.header_set h_set = self.header_set
# Does the app want us to send output chunked? # Does the app want us to send output chunked?
self.chunked = h_set.get('transfer-encoding', '').lower() == 'chunked' self.chunked = h_set.get('Transfer-Encoding', '').lower() == 'chunked'
# Add a Date header if it's not there already # Add a Date header if it's not there already
if not 'date' in h_set: if not 'Date' in h_set:
h_set['Date'] = formatdate(usegmt=True) h_set['Date'] = formatdate(usegmt=True)
# Add a Server header if it's not there already # Add a Server header if it's not there already
if not 'server' in h_set: if not 'Server' in h_set:
h_set['Server'] = HTTP_SERVER_SOFTWARE h_set['Server'] = HTTP_SERVER_SOFTWARE
if 'content-length' in h_set: if 'Content-Length' in h_set:
self.size = int(h_set['content-length']) self.size = int(h_set['Content-Length'])
else: else:
s = int(self.status.split(' ')[0]) s = int(self.status.split(' ')[0])
if s < 200 or s not in (204, 205, 304): if (s < 200 or s not in (204, 205, 304)) and not self.chunked:
if not self.chunked: if sections == 1 or self.protocol != 'HTTP/1.1':
if sections == 1: # Add a Content-Length header because it's not there
# Add a Content-Length header if it's not there already self.size = len(data)
h_set['Content-Length'] = str(len(data)) h_set['Content-Length'] = str(self.size)
self.size = len(data) else:
else: # If they sent us more than one section, we blow chunks
# If they sent us more than one section, we blow chunks h_set['Transfer-Encoding'] = 'Chunked'
h_set['Transfer-Encoding'] = 'Chunked' self.chunked = True
self.chunked = True if __debug__:
if __debug__: self.err_log.debug('Adding header...'
self.err_log.debug('Adding header...' 'Transfer-Encoding: Chunked')
'Transfer-Encoding: Chunked')
if 'connection' not in h_set: if 'Connection' not in h_set:
# If the application did not provide a connection header, fill it in # If the application did not provide a connection header,
# fill it in
client_conn = self.environ.get('HTTP_CONNECTION', '').lower() client_conn = self.environ.get('HTTP_CONNECTION', '').lower()
if self.environ['SERVER_PROTOCOL'] == 'HTTP/1.1': if self.environ['SERVER_PROTOCOL'] == 'HTTP/1.1':
# HTTP = 1.1 defaults to keep-alive connections # HTTP = 1.1 defaults to keep-alive connections
@@ -1722,11 +1734,12 @@ class WSGIWorker(Worker):
else: else:
h_set['Connection'] = 'keep-alive' h_set['Connection'] = 'keep-alive'
else: else:
# HTTP < 1.1 supports keep-alive but it's quirky so we don't support it # HTTP < 1.1 supports keep-alive but it's quirky
# so we don't support it
h_set['Connection'] = 'close' h_set['Connection'] = 'close'
# Close our connection if we need to. # Close our connection if we need to.
self.closeConnection = h_set.get('connection', '').lower() == 'close' self.closeConnection = h_set.get('Connection', '').lower() == 'close'
# Build our output headers # Build our output headers
header_data = HEADER_RESPONSE % (self.status, str(h_set)) header_data = HEADER_RESPONSE % (self.status, str(h_set))
@@ -1758,6 +1771,8 @@ class WSGIWorker(Worker):
self.conn.sendall(b('%x\r\n%s\r\n' % (len(data), data))) self.conn.sendall(b('%x\r\n%s\r\n' % (len(data), data)))
else: else:
self.conn.sendall(data) self.conn.sendall(data)
except socket.timeout:
self.closeConnection = True
except socket.error: except socket.error:
# But some clients will close the connection before that # But some clients will close the connection before that
# resulting in a socket error. # resulting in a socket error.
@@ -1853,55 +1868,4 @@ class WSGIWorker(Worker):
sock_file.close() sock_file.close()
# Monolithic build...end of module: rocket\methods\wsgi.py # Monolithic build...end of module: rocket/methods/wsgi.py
#
# the following code is not part of Rocket but was added in web2py for testing purposes
#
def demo_app(environ, start_response):
global static_folder
import os
types = {'htm': 'text/html', 'html': 'text/html', 'gif': 'image/gif',
'jpg': 'image/jpeg', 'png': 'image/png', 'pdf': 'applications/pdf'}
if static_folder:
if not static_folder.startswith('/'):
static_folder = os.path.join(os.getcwd(), static_folder)
path = os.path.join(
static_folder, environ['PATH_INFO'][1:] or 'index.html')
type = types.get(path.split('.')[-1], 'text')
if os.path.exists(path):
try:
data = open(path, 'rb').read()
start_response('200 OK', [('Content-Type', type)])
except IOError:
start_response('404 NOT FOUND', [])
data = '404 NOT FOUND'
else:
start_response('500 INTERNAL SERVER ERROR', [])
data = '500 INTERNAL SERVER ERROR'
else:
start_response('200 OK', [('Content-Type', 'text/html')])
data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>'
return [data]
def demo():
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-i", "--ip", dest="ip", default="127.0.0.1",
help="ip address of the network interface")
parser.add_option("-p", "--port", dest="port", default="8000",
help="post where to run web server")
parser.add_option("-s", "--static", dest="static", default=None,
help="folder containing static files")
(options, args) = parser.parse_args()
global static_folder
static_folder = options.static
print 'Rocket running on %s:%s' % (options.ip, options.port)
r = Rocket((options.ip, int(options.port)), 'wsgi', {'wsgi_app': demo_app})
r.start()
if __name__ == '__main__':
demo()
+18 -15
View File
@@ -1,21 +1,22 @@
# The following code is not part of Rocket but was added to
# web2py for testing purposes.
#
# the following code is not part of Rocket but was added in web2py for testing purposes
#
def demo_app(environ, start_response): def demo_app(environ, start_response):
global static_folder global static_folder
import os import os
types = {'htm': 'text/html','html': 'text/html','gif': 'image/gif', types = {'htm': 'text/html', 'html': 'text/html',
'jpg': 'image/jpeg','png': 'image/png','pdf': 'applications/pdf'} 'gif': 'image/gif', 'jpg': 'image/jpeg',
'png': 'image/png', 'pdf': 'applications/pdf'}
if static_folder: if static_folder:
if not static_folder.startswith('/'): if not static_folder.startswith('/'):
static_folder = os.path.join(os.getcwd(),static_folder) static_folder = os.path.join(os.getcwd(), static_folder)
path = os.path.join(static_folder, environ['PATH_INFO'][1:] or 'index.html') path = os.path.join(
type = types.get(path.split('.')[-1],'text') static_folder, environ['PATH_INFO'][1:] or 'index.html')
type = types.get(path.split('.')[-1], 'text')
if os.path.exists(path): if os.path.exists(path):
try: try:
data = open(path,'rb').read() data = open(path, 'rb').read()
start_response('200 OK', [('Content-Type', type)]) start_response('200 OK', [('Content-Type', type)])
except IOError: except IOError:
start_response('404 NOT FOUND', []) start_response('404 NOT FOUND', [])
@@ -25,24 +26,26 @@ def demo_app(environ, start_response):
data = '500 INTERNAL SERVER ERROR' data = '500 INTERNAL SERVER ERROR'
else: else:
start_response('200 OK', [('Content-Type', 'text/html')]) start_response('200 OK', [('Content-Type', 'text/html')])
data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>' data = '<html><body><h1>Hello from Rocket</h1></body></html>'
return [data] return [data]
def demo(): def demo():
from optparse import OptionParser from optparse import OptionParser
parser = OptionParser() parser = OptionParser()
parser.add_option("-i", "--ip", dest="ip",default="127.0.0.1", parser.add_option("-i", "--ip", dest="ip", default="127.0.0.1",
help="ip address of the network interface") help="ip address of the network interface")
parser.add_option("-p", "--port", dest="port",default="8000", parser.add_option("-p", "--port", dest="port", default="8000",
help="post where to run web server") help="post where to run web server")
parser.add_option("-s", "--static", dest="static",default=None, parser.add_option("-s", "--static", dest="static", default=None,
help="folder containing static files") help="folder containing static files")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
global static_folder global static_folder
static_folder = options.static static_folder = options.static
print 'Rocket running on %s:%s' % (options.ip, options.port) print 'Rocket running on %s:%s' % (options.ip, options.port)
r=Rocket((options.ip,int(options.port)),'wsgi', {'wsgi_app':demo_app}) r = Rocket((options.ip, int(options.port)), 'wsgi', {'wsgi_app': demo_app})
r.start() r.start()
if __name__=='__main__':
if __name__ == '__main__':
demo() demo()
+14 -1
View File
@@ -263,7 +263,7 @@ def is_valid_ip_address(address):
# deal with special cases # deal with special cases
if address.lower() in ('127.0.0.1', 'localhost', '::1', '::ffff:127.0.0.1'): if address.lower() in ('127.0.0.1', 'localhost', '::1', '::ffff:127.0.0.1'):
return True return True
elif address.lower() in ('unkown', ''): elif address.lower() in ('unknown', ''):
return False return False
elif address.count('.') == 3: # assume IPv4 elif address.count('.') == 3: # assume IPv4
if address.startswith('::ffff:'): if address.startswith('::ffff:'):
@@ -287,3 +287,16 @@ def is_valid_ip_address(address):
return False return False
else: # do not know what to do? assume it is a valid address else: # do not know what to do? assume it is a valid address
return True return True
def is_loopback_ip_address(ip):
"""Determines whether the IP address appears to be a loopback address.
This assumes that the IP is valid. The IPv6 check is limited to '::1'.
"""
if not ip:
return False
if ip.count('.') == 3: # IPv4
return ip.startswith('127') or ip.startswith('::ffff:127')
return ip == '::1' # IPv6
+93 -60
View File
@@ -28,6 +28,7 @@ import main
from fileutils import w2p_pack, read_file, write_file from fileutils import w2p_pack, read_file, write_file
from settings import global_settings from settings import global_settings
from shell import run, test from shell import run, test
from utils import is_valid_ip_address, is_loopback_ip_address
try: try:
import Tkinter import Tkinter
@@ -99,26 +100,34 @@ class IO(object):
self.buffer.write(data) self.buffer.write(data)
def try_start_browser(url): def get_url(host, path='/', proto='http', port=80):
""" Try to start the default browser """ if ':' in host:
host = '[%s]' % host
else:
host = host.replace('0.0.0.0', '127.0.0.1')
if path.startswith('/'):
path = path[1:]
if proto.endswith(':'):
proto = proto[:-1]
if not port or port == 80:
port = ''
else:
port = ':%s' % port
return '%s://%s%s/%s' % (proto, host, port, path)
def start_browser(url, startup=False):
if startup:
print 'please visit:'
print '\t', url
print 'starting browser...'
try: try:
import webbrowser import webbrowser
url = url.replace('0.0.0.0','127.0.0.1')
webbrowser.open(url) webbrowser.open(url)
except: except:
print 'warning: unable to detect your browser' print 'warning: unable to detect your browser'
def start_browser(proto, ip, port):
""" Starts the default browser """
print 'please visit:'
url = '%s://%s:%s' % (proto, ip, port)
print '\t', url
print 'starting browser...'
try_start_browser(url)
def presentation(root): def presentation(root):
""" Draw the splash screen """ """ Draw the splash screen """
@@ -186,7 +195,7 @@ class web2pyDialog(object):
httplog = os.path.join(self.options.folder, 'httpserver.log') httplog = os.path.join(self.options.folder, 'httpserver.log')
# Building the Menu # Building the Menu
item = lambda: try_start_browser(httplog) item = lambda: start_browser(httplog)
servermenu.add_command(label='View httpserver.log', servermenu.add_command(label='View httpserver.log',
command=item) command=item)
@@ -207,7 +216,7 @@ class web2pyDialog(object):
helpmenu = Tkinter.Menu(self.menu, tearoff=0) helpmenu = Tkinter.Menu(self.menu, tearoff=0)
# Home Page # Home Page
item = lambda: try_start_browser('http://www.web2py.com') item = lambda: start_browser('http://www.web2py.com/')
helpmenu.add_command(label='Home Page', helpmenu.add_command(label='Home Page',
command=item) command=item)
@@ -237,7 +246,8 @@ class web2pyDialog(object):
self.ips = {} self.ips = {}
self.selected_ip = Tkinter.StringVar() self.selected_ip = Tkinter.StringVar()
row = 0 row = 0
ips = [('127.0.0.1', 'Local')] + \ ips = [('127.0.0.1', 'Local (IPv4)')] + \
[('::1', 'Local (IPv6)')] if socket.has_ipv6 else [] + \
[(ip, 'Public') for ip in options.ips] + \ [(ip, 'Public') for ip in options.ips] + \
[('0.0.0.0', 'Public')] [('0.0.0.0', 'Public')]
for ip, legend in ips: for ip, legend in ips:
@@ -406,10 +416,9 @@ class web2pyDialog(object):
if os.path.exists('applications/%s/__init__.py' % arq)] if os.path.exists('applications/%s/__init__.py' % arq)]
self.pagesmenu.delete(0, len(available_apps)) self.pagesmenu.delete(0, len(available_apps))
for arq in available_apps: for arq in available_apps:
url = self.url + '/' + arq url = self.url + arq
start_browser = lambda u = url: try_start_browser(u)
self.pagesmenu.add_command(label=url, self.pagesmenu.add_command(label=url,
command=start_browser) command=lambda u=url: start_browser(u))
def quit(self, justHide=False): def quit(self, justHide=False):
""" Finish the program execution """ """ Finish the program execution """
@@ -449,8 +458,7 @@ class web2pyDialog(object):
ip = self.selected_ip.get() ip = self.selected_ip.get()
regexp = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' if not is_valid_ip_address(ip):
if ip and not re.compile(regexp).match(ip):
return self.error('invalid host ip address') return self.error('invalid host ip address')
try: try:
@@ -464,7 +472,7 @@ class web2pyDialog(object):
else: else:
proto = 'http' proto = 'http'
self.url = '%s://%s:%s' % (proto, ip, port) self.url = get_url(ip, proto=proto, port=port)
self.connect_pages() self.connect_pages()
self.button_start.configure(state='disabled') self.button_start.configure(state='disabled')
@@ -501,7 +509,9 @@ class web2pyDialog(object):
self.button_stop.configure(state='normal') self.button_stop.configure(state='normal')
if not options.taskbar: if not options.taskbar:
thread.start_new_thread(start_browser, (proto, ip, port)) thread.start_new_thread(start_browser,
(get_url(ip, proto=proto, port=port),),
dict(startup=True))
self.password.configure(state='readonly') self.password.configure(state='readonly')
[ip.configure(state='disabled') for ip in self.ips.values()] [ip.configure(state='disabled') for ip in self.ips.values()]
@@ -584,11 +594,13 @@ def console():
parser.description = description parser.description = description
msg = ('IP address of the server (e.g., 127.0.0.1 or ::1); '
'Note: This value is ignored when using the \'interfaces\' option.')
parser.add_option('-i', parser.add_option('-i',
'--ip', '--ip',
default='127.0.0.1', default='127.0.0.1',
dest='ip', dest='ip',
help='ip address of the server (127.0.0.1)') help=msg)
parser.add_option('-p', parser.add_option('-p',
'--port', '--port',
@@ -597,8 +609,8 @@ def console():
type='int', type='int',
help='port of server (8000)') help='port of server (8000)')
msg = 'password to be used for administration' msg = ('password to be used for administration '
msg += ' (use -a "<recycle>" to reuse the last password))' '(use -a "<recycle>" to reuse the last password))')
parser.add_option('-a', parser.add_option('-a',
'--password', '--password',
default='<ask>', default='<ask>',
@@ -617,11 +629,13 @@ def console():
dest='ssl_private_key', dest='ssl_private_key',
help='file that contains ssl private key') help='file that contains ssl private key')
msg = ('Use this file containing the CA certificate to validate X509 '
'certificates from clients')
parser.add_option('--ca-cert', parser.add_option('--ca-cert',
action='store', action='store',
dest='ssl_ca_certificate', dest='ssl_ca_certificate',
default=None, default=None,
help='Use this file containing the CA certificate to validate X509 certificates from clients') help=msg)
parser.add_option('-d', parser.add_option('-d',
'--pid_filename', '--pid_filename',
@@ -708,8 +722,8 @@ def console():
default=False, default=False,
help='disable all output') help='disable all output')
msg = 'set debug output level (0-100, 0 means all, 100 means none;' msg = ('set debug output level (0-100, 0 means all, 100 means none; '
msg += ' default is 30)' 'default is 30)')
parser.add_option('-D', parser.add_option('-D',
'--debug', '--debug',
dest='debuglevel', dest='debuglevel',
@@ -717,18 +731,18 @@ def console():
type='int', type='int',
help=msg) help=msg)
msg = 'run web2py in interactive shell or IPython (if installed) with' msg = ('run web2py in interactive shell or IPython (if installed) with '
msg += ' specified appname (if app does not exist it will be created).' 'specified appname (if app does not exist it will be created). '
msg += ' APPNAME like a/c/f (c,f optional)' 'APPNAME like a/c/f (c,f optional)')
parser.add_option('-S', parser.add_option('-S',
'--shell', '--shell',
dest='shell', dest='shell',
metavar='APPNAME', metavar='APPNAME',
help=msg) help=msg)
msg = 'run web2py in interactive shell or bpython (if installed) with' msg = ('run web2py in interactive shell or bpython (if installed) with '
msg += ' specified appname (if app does not exist it will be created).' 'specified appname (if app does not exist it will be created).\n'
msg += '\n Use combined with --shell' 'Use combined with --shell')
parser.add_option('-B', parser.add_option('-B',
'--bpython', '--bpython',
action='store_true', action='store_true',
@@ -744,8 +758,8 @@ def console():
dest='plain', dest='plain',
help=msg) help=msg)
msg = 'auto import model files; default is False; should be used' msg = ('auto import model files; default is False; should be used '
msg += ' with --shell option' 'with --shell option')
parser.add_option('-M', parser.add_option('-M',
'--import_models', '--import_models',
action='store_true', action='store_true',
@@ -753,8 +767,8 @@ def console():
dest='import_models', dest='import_models',
help=msg) help=msg)
msg = 'run PYTHON_FILE in web2py environment;' msg = ('run PYTHON_FILE in web2py environment; '
msg += ' should be used with --shell option' 'should be used with --shell option')
parser.add_option('-R', parser.add_option('-R',
'--run', '--run',
dest='run', dest='run',
@@ -762,11 +776,11 @@ def console():
default='', default='',
help=msg) help=msg)
msg = 'run scheduled tasks for the specified apps: expects a list of ' msg = ('run scheduled tasks for the specified apps: expects a list of '
msg += 'app names as -K app1,app2,app3 ' 'app names as -K app1,app2,app3 '
msg += 'or a list of app:groups as -K app1:group1:group2,app2:group1 ' 'or a list of app:groups as -K app1:group1:group2,app2:group1 '
msg += 'to override specific group_names. (only strings, no spaces ' 'to override specific group_names. (only strings, no spaces '
msg += 'allowed. Requires a scheduler defined in the models' 'allowed. Requires a scheduler defined in the models')
parser.add_option('-K', parser.add_option('-K',
'--scheduler', '--scheduler',
dest='scheduler', dest='scheduler',
@@ -781,8 +795,8 @@ def console():
dest='with_scheduler', dest='with_scheduler',
help=msg) help=msg)
msg = 'run doctests in web2py environment; ' +\ msg = ('run doctests in web2py environment; '
'TEST_PATH like a/c/f (c,f optional)' 'TEST_PATH like a/c/f (c,f optional)')
parser.add_option('-T', parser.add_option('-T',
'--test', '--test',
dest='test', dest='test',
@@ -851,12 +865,14 @@ def console():
dest='nogui', dest='nogui',
help='text-only, no GUI') help='text-only, no GUI')
msg = ('should be followed by a list of arguments to be passed to script, '
'to be used with -S, -A must be the last option')
parser.add_option('-A', parser.add_option('-A',
'--args', '--args',
action='store', action='store',
dest='args', dest='args',
default=None, default=None,
help='should be followed by a list of arguments to be passed to script, to be used with -S, -A must be the last option') help=msg)
parser.add_option('--no-banner', parser.add_option('--no-banner',
action='store_true', action='store_true',
@@ -864,7 +880,10 @@ def console():
dest='nobanner', dest='nobanner',
help='Do not print header banner') help='Do not print header banner')
msg = 'listen on multiple addresses: "ip:port:cert:key:ca_cert;ip2:port2:cert2:key2:ca_cert2;..." (:cert:key optional; no spaces)' msg = ('listen on multiple addresses: '
'"ip1:port1:key1:cert1:ca_cert1;ip2:port2:key2:cert2:ca_cert2;..." '
'(:key:cert:ca_cert optional; no spaces; IPv6 addresses must be in '
'square [] brackets)')
parser.add_option('--interfaces', parser.add_option('--interfaces',
action='store', action='store',
dest='interfaces', dest='interfaces',
@@ -891,9 +910,9 @@ def console():
global_settings.cmd_args = args global_settings.cmd_args = args
try: try:
options.ips = [ options.ips = list(set([
ip for ip in socket.gethostbyname_ex(socket.getfqdn())[2] ip[4][0] for ip in socket.getaddrinfo(socket.getfqdn(), 0)
if ip != '127.0.0.1'] if not is_loopback_ip_address(ip[4][0])]))
except socket.gaierror: except socket.gaierror:
options.ips = [] options.ips = []
@@ -920,15 +939,22 @@ def console():
options.folder = os.path.abspath(options.folder) options.folder = os.path.abspath(options.folder)
# accept --interfaces in the form # accept --interfaces in the form
# "ip:port:cert:key;ip2:port2;ip3:port3:cert3:key3" # "ip1:port1:key1:cert1:ca_cert1;[ip2]:port2;ip3:port3:key3:cert3"
# (no spaces; optional cert:key indicate SSL) # (no spaces; optional key:cert indicate SSL)
if isinstance(options.interfaces, str): if isinstance(options.interfaces, str):
options.interfaces = [ interfaces = options.interfaces.split(';')
interface.split(':') for interface in options.interfaces.split(';')] options.interfaces = []
for interface in options.interfaces: for interface in interfaces:
interface[1] = int(interface[1]) # numeric port if interface.startswith('['): # IPv6
options.interfaces = [ ip, if_remainder = interface.split(']', 1)
tuple(interface) for interface in options.interfaces] ip = ip[1:]
if_remainder = if_remainder[1:].split(':')
if_remainder[0] = int(if_remainder[0]) # numeric port
options.interfaces.append(tuple([ip] + if_remainder))
else: # IPv4
interface = interface.split(':')
interface[1] = int(interface[1]) # numeric port
options.interfaces.append(tuple(interface))
# accepts --scheduler in the form # accepts --scheduler in the form
# "app:group1,group2,app2:group1" # "app:group1,group2,app2:group1"
@@ -1189,14 +1215,21 @@ end tell
# ## start server # ## start server
(ip, port) = (options.ip, int(options.port)) # Use first interface IP and port if interfaces specified, since the
# interfaces option overrides the IP (and related) options.
if not options.interfaces:
(ip, port) = (options.ip, int(options.port))
else:
first_if = options.interfaces[0]
(ip, port) = first_if[0], first_if[1]
# Check for non default value for ssl inputs # Check for non default value for ssl inputs
if (len(options.ssl_certificate) > 0) or (len(options.ssl_private_key) > 0): if (len(options.ssl_certificate) > 0) or (len(options.ssl_private_key) > 0):
proto = 'https' proto = 'https'
else: else:
proto = 'http' proto = 'http'
url = '%s://%s:%s' % (proto, ip, port)
url = get_url(ip, proto=proto, port=port)
if not options.nobanner: if not options.nobanner:
print 'please visit:' print 'please visit:'