Commit 77b3114d authored by Denis Bilenko's avatar Denis Bilenko

pywsgi: get rid of the dependency BaseHTTPServer. Based on the patch by Ralf Schmitt

parent ccf96afa
...@@ -5,10 +5,11 @@ import errno ...@@ -5,10 +5,11 @@ import errno
import sys import sys
import time import time
import traceback import traceback
import mimetools
from datetime import datetime
from urllib import unquote from urllib import unquote
from gevent import socket from gevent import socket
import BaseHTTPServer
import gevent import gevent
from gevent.server import StreamServer from gevent.server import StreamServer
from gevent.hub import GreenletExit from gevent.hub import GreenletExit
...@@ -31,6 +32,8 @@ _INTERNAL_ERROR_BODY = 'Internal Server Error' ...@@ -31,6 +32,8 @@ _INTERNAL_ERROR_BODY = 'Internal Server Error'
_INTERNAL_ERROR_HEADERS = [('Content-Type', 'text/plain'), _INTERNAL_ERROR_HEADERS = [('Content-Type', 'text/plain'),
('Connection', 'close'), ('Connection', 'close'),
('Content-Length', str(len(_INTERNAL_ERROR_BODY)))] ('Content-Length', str(len(_INTERNAL_ERROR_BODY)))]
_REQUEST_TOO_LONG_RESPONSE = "HTTP/1.0 414 Request URI Too Long\r\nConnection: close\r\nContent-length: 0\r\n\r\n"
_BAD_REQUEST_RESPONSE = "HTTP/1.0 400 Bad Request\r\nConnection: close\r\nContent-length: 0\r\n\r\n"
def format_date_time(timestamp): def format_date_time(timestamp):
...@@ -42,8 +45,6 @@ class Input(object): ...@@ -42,8 +45,6 @@ class Input(object):
def __init__(self, rfile, content_length, wfile=None, wfile_line=None, chunked_input=False): def __init__(self, rfile, content_length, wfile=None, wfile_line=None, chunked_input=False):
self.rfile = rfile self.rfile = rfile
if content_length is not None:
content_length = int(content_length)
self.content_length = content_length self.content_length = content_length
self.wfile = wfile self.wfile = wfile
self.wfile_line = wfile_line self.wfile_line = wfile_line
...@@ -109,42 +110,143 @@ class Input(object): ...@@ -109,42 +110,143 @@ class Input(object):
return list(self) return list(self)
def __iter__(self): def __iter__(self):
while True: return self
def next(self):
line = self.readline() line = self.readline()
if not line: if not line:
break raise StopIteration
yield line return line
class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler): class WSGIHandler(object):
protocol_version = 'HTTP/1.1' protocol_version = 'HTTP/1.1'
MessageClass = mimetools.Message
def handle_one_request(self): def __init__(self, socket, address, server):
if self.rfile.closed: self.socket = socket
self.close_connection = 1 self.client_address = address
return self.server = server
self.rfile = socket.makefile('rb', -1)
self.wfile = socket.makefile('wb', 0)
def handle(self):
try: try:
self.raw_requestline = self.rfile.readline(MAX_REQUEST_LINE) while True:
if len(self.raw_requestline) == MAX_REQUEST_LINE: self.time_start = time.time()
self.status = '414' self.time_finish = 0
self.wfile.write( result = self.handle_one_request()
"HTTP/1.0 414 Request URI Too Long\r\nConnection: close\r\nContent-length: 0\r\n\r\n") if result is None:
self.close_connection = 1 break
if result is True:
continue
self.status, response_body = result
self.wfile.write(response_body)
if self.time_finish == 0:
self.time_finish = time.time()
self.log_request() self.log_request()
break
finally:
self.__dict__.pop('socket', None)
self.__dict__.pop('rfile', None)
self.__dict__.pop('wfile', None)
def _check_http_version(self):
version = self.request_version
if not version.startswith("HTTP/"):
return False
version = tuple(int(x) for x in version[5:].split(".")) # "HTTP/"
if version[1] < 0 or version < (0, 9) or version >= (2, 0):
return False
return True
def read_request(self, raw_requestline):
self.requestline = raw_requestline.rstrip()
words = self.requestline.split()
if len(words) == 3:
self.command, self.path, self.request_version = words
if not self._check_http_version():
self.log_error('Invalid http version: %r', raw_requestline)
return
elif len(words) == 2:
self.command, self.path = words
if self.command != "GET":
self.log_error('Expected GET method: %r', raw_requestline)
return
self.request_version = "HTTP/0.9"
else:
self.log_error('Invalid GET method: %r', raw_requestline)
return return
except socket.error, e:
if e[0] != errno.EBADF and e[0] != errno.ECONNRESET:
raise
self.raw_requestline = ''
if not self.raw_requestline: self.headers = self.MessageClass(self.rfile, 0)
self.close_connection = 1 if self.headers.status:
self.log_error('Invalid headers status: %r', self.headers.status)
return return
if not self.parse_request(): content_length = self.headers.get("Content-Length")
if content_length is not None:
content_length = int(content_length)
if content_length < 0:
self.log_error('Invalid Content-Length: %r', content_length)
return
if content_length and self.command in ('GET', 'HEAD'):
self.log_error('Unexpected Content-Length')
return return
self.content_length = content_length
if self.request_version == "HTTP/1.1":
conntype = self.headers.get("Connection", "").lower()
if conntype == "close":
self.close_connection = True
else:
self.close_connection = False
else:
self.close_connection = True
return True
def log_error(self, msg, *args):
try:
message = msg % args
except Exception:
traceback.print_exc()
message = '%r %r' % (msg, args)
sys.exc_clear()
try:
message = '%s: %s' % (self.socket, message)
except Exception:
sys.exc_clear()
try:
sys.stderr.write(message + '\n')
except Exception:
traceback.print_exc()
sys.exc_clear()
def handle_one_request(self):
if self.rfile.closed:
return
raw_requestline = self.rfile.readline(MAX_REQUEST_LINE)
if not raw_requestline:
return
self.response_length = 0
if len(raw_requestline) >= MAX_REQUEST_LINE:
return ('414', _REQUEST_TOO_LONG_RESPONSE)
try:
if not self.read_request(raw_requestline):
return ('400', _BAD_REQUEST_RESPONSE)
except ValueError, ex:
self.log_error('Invalid request: %s', str(ex) or ex.__class__.__name__)
return ('400', _BAD_REQUEST_RESPONSE)
except Exception, ex:
traceback.print_exc()
self.log_error('Invalid request: %s', str(ex) or ex.__class__.__name__)
return ('400', _BAD_REQUEST_RESPONSE)
self.environ = self.get_environ() self.environ = self.get_environ()
self.application = self.server.application self.application = self.server.application
try: try:
...@@ -156,6 +258,14 @@ class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler): ...@@ -156,6 +258,14 @@ class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
else: else:
raise raise
if self.close_connection:
return
if self.rfile.closed:
return
return True # read more requests
def write(self, data): def write(self, data):
towrite = [] towrite = []
if not self.status: if not self.status:
...@@ -212,15 +322,16 @@ class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler): ...@@ -212,15 +322,16 @@ class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.response_headers_list = [x[0] for x in self.response_headers] self.response_headers_list = [x[0] for x in self.response_headers]
return self.write return self.write
def log_request(self, *args): def log_request(self):
log = self.server.log log = self.server.log
if log is not None: if log is not None:
log.write(self.format_request(*args) + '\n') log.write(self.format_request() + '\n')
def format_request(self, length='-'): def format_request(self):
now = datetime.now().replace(microsecond=0)
return '%s - - [%s] "%s" %s %s %.6f' % ( return '%s - - [%s] "%s" %s %s %.6f' % (
self.client_address[0], self.client_address[0],
self.log_date_time_string(), now,
self.requestline, self.requestline,
(self.status or '000').split()[0], (self.status or '000').split()[0],
self.response_length, self.response_length,
...@@ -295,23 +406,22 @@ class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler): ...@@ -295,23 +406,22 @@ class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
env['CONTENT_LENGTH'] = length env['CONTENT_LENGTH'] = length
env['SERVER_PROTOCOL'] = 'HTTP/1.0' env['SERVER_PROTOCOL'] = 'HTTP/1.0'
host, port = self.request.getsockname() host, port = self.socket.getsockname()
env['SERVER_NAME'] = host env['SERVER_NAME'] = host
env['SERVER_PORT'] = str(port) env['SERVER_PORT'] = str(port)
env['REMOTE_ADDR'] = self.client_address[0] env['REMOTE_ADDR'] = self.client_address[0]
env['GATEWAY_INTERFACE'] = 'CGI/1.1' env['GATEWAY_INTERFACE'] = 'CGI/1.1'
for h in self.headers.headers: for header in self.headers.headers:
k, v = h.split(':', 1) key, value = header.split(':', 1)
k = k.replace('-', '_').upper() key = key.replace('-', '_').upper()
v = v.strip() if key not in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
if k in env: value = value.strip()
continue key = 'HTTP_' + key
envk = 'HTTP_' + k if key in env:
if envk in env: env[key] += ',' + value
env[envk] += ',' + v
else: else:
env[envk] = v env[key] = value
if env.get('HTTP_EXPECT') == '100-continue': if env.get('HTTP_EXPECT') == '100-continue':
wfile = self.wfile wfile = self.wfile
...@@ -320,14 +430,10 @@ class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler): ...@@ -320,14 +430,10 @@ class WSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
wfile = None wfile = None
wfile_line = None wfile_line = None
chunked = env.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked' chunked = env.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked'
self.wsgi_input = Input(self.rfile, length, wfile=wfile, wfile_line=wfile_line, chunked_input=chunked) self.wsgi_input = Input(self.rfile, self.content_length, wfile=wfile, wfile_line=wfile_line, chunked_input=chunked)
env['wsgi.input'] = self.wsgi_input env['wsgi.input'] = self.wsgi_input
return env return env
def finish(self):
BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
self.connection.close()
class WSGIServer(StreamServer): class WSGIServer(StreamServer):
"""A WSGI server based on :class:`StreamServer` that supports HTTPS.""" """A WSGI server based on :class:`StreamServer` that supports HTTPS."""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment