Changes for Python 3: str/bytes fixes mostly.

This commit is contained in:
Neil Schemenauer 2016-03-31 20:40:32 +00:00
parent 9c12cd7b1a
commit 574b9b1088
13 changed files with 153 additions and 160 deletions

View File

@ -38,7 +38,7 @@ reference.
""" """
import re import re
import urllib import urllib.request, urllib.parse, urllib.error
try: try:
# faster C implementation # faster C implementation
@ -103,7 +103,7 @@ def url_quote(value, fallback=None):
raise ValueError("value is None and no fallback supplied") raise ValueError("value is None and no fallback supplied")
else: else:
return fallback return fallback
return urllib.quote(stringify(value)) return urllib.parse.quote(stringify(value))
_saved = None _saved = None
def use_qpy(): def use_qpy():
@ -111,7 +111,7 @@ def use_qpy():
Switch to using 'qpy' as an alternative. Switch to using 'qpy' as an alternative.
""" """
import qpy import qpy
from qpy_templateio import qpy_TemplateIO from .qpy_templateio import qpy_TemplateIO
global _saved, htmltext, stringify, htmlescape, TemplateIO global _saved, htmltext, stringify, htmlescape, TemplateIO
if not _saved: if not _saved:

View File

@ -3,8 +3,10 @@ TemplateIO.
""" """
def _escape_string(s): def _escape_string(s):
if not isinstance(s, basestring): if isinstance(s, bytes):
raise TypeError, 'string object required' raise TypeError('escape_string no longer accepts bytes')
if not isinstance(s, str):
raise TypeError('string object required')
s = s.replace("&", "&") s = s.replace("&", "&")
s = s.replace("<", "&lt;") s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;") s = s.replace(">", "&gt;")
@ -16,17 +18,12 @@ def stringify(obj):
turning strings into unicode objects. turning strings into unicode objects.
""" """
tp = type(obj) tp = type(obj)
if issubclass(tp, basestring): if isinstance(obj, (str, bytes)):
return obj return obj
elif hasattr(tp, '__unicode__'):
s = tp.__unicode__(obj)
if not isinstance(s, basestring):
raise TypeError, '__unicode__ did not return a string'
return s
elif hasattr(tp, '__str__'): elif hasattr(tp, '__str__'):
s = tp.__str__(obj) s = tp.__str__(obj)
if not isinstance(s, basestring): if not isinstance(s, str):
raise TypeError, '__str__ did not return a string' raise TypeError('__str__ did not return a string')
return s return s
else: else:
return str(obj) return str(obj)
@ -58,8 +55,11 @@ class htmltext(object):
def __len__(self): def __len__(self):
return len(self.s) return len(self.s)
def __cmp__(self, other): def __eq__(self, other):
return cmp(self.s, other) return (self.s == other)
def __lt__(self, other):
return (self.s < other)
def __hash__(self): def __hash__(self):
return hash(self.s) return hash(self.s)
@ -156,17 +156,6 @@ class _QuoteWrapper(object):
def __getitem__(self, key): def __getitem__(self, key):
return _wraparg(self.value[key]) return _wraparg(self.value[key])
class _UnicodeWrapper(unicode):
__slots__ = ['raw']
def __new__(cls, s):
result = unicode.__new__(cls, _escape_string(s))
result.raw = s
return result
def __repr__(self):
return _escape_string(`self.raw`)
def _wraparg(arg): def _wraparg(arg):
@ -176,7 +165,7 @@ def _wraparg(arg):
return stringify(arg) return stringify(arg)
elif isinstance(arg, str): elif isinstance(arg, str):
# again, work around PyString_Format bug # again, work around PyString_Format bug
return _UnicodeWrapper(arg) return _escape_string(arg)
elif (isinstance(arg, int) or elif (isinstance(arg, int) or
isinstance(arg, float)): isinstance(arg, float)):
# ints, floats are okay # ints, floats are okay

View File

@ -5,10 +5,13 @@ from quixote.html import href, url_with_query, url_quote, nl2br
markupchars = '<>&"' markupchars = '<>&"'
quotedchars = '&lt;&gt;&amp;&quot;' quotedchars = '&lt;&gt;&amp;&quot;'
if sys.hexversion >= 0x20400a2: unicodechars = '\\u1234'
unicodechars = u'\u1234'
else: # Byte types...
unicodechars = 'x' # lie, Python <= 2.3 is broken markupbytes = b'<>&"'
quotedbytes = b'&lt;&gt;&amp;&quot;'
bytebytes = b'\u1234'
class Wrapper: class Wrapper:
def __init__(self, s): def __init__(self, s):
@ -68,8 +71,8 @@ class HTMLTextTest (UTest):
def _check_init(self): def _check_init(self):
assert str(htmltext('foo')) == 'foo' assert str(htmltext('foo')) == 'foo'
assert str(htmltext(markupchars)) == markupchars assert str(htmltext(markupchars)) == markupchars
assert unicode(htmltext(unicodechars)) == unicodechars assert str(htmltext(unicodechars)) == unicodechars
assert str(htmltext(unicode(markupchars))) == markupchars assert str(htmltext(str(markupchars))) == markupchars
assert str(htmltext(None)) == 'None' assert str(htmltext(None)) == 'None'
assert str(htmltext(1)) == '1' assert str(htmltext(1)) == '1'
try: try:
@ -84,19 +87,31 @@ class HTMLTextTest (UTest):
assert stringify(Wrapper(markupchars)) is markupchars assert stringify(Wrapper(markupchars)) is markupchars
assert stringify(Wrapper) == str(Wrapper) assert stringify(Wrapper) == str(Wrapper)
assert stringify(None) == str(None) assert stringify(None) == str(None)
assert stringify(markupbytes) is markupbytes
def check_escape(self): def check_escape(self):
assert htmlescape(markupchars) == quotedchars assert htmlescape(markupchars) == quotedchars
assert isinstance(htmlescape(markupchars), htmltext) assert isinstance(htmlescape(markupchars), htmltext)
assert escape(markupchars) == quotedchars assert escape(markupchars) == quotedchars
assert escape(unicodechars) == unicodechars assert escape(unicodechars) == unicodechars
assert escape(unicode(markupchars)) == quotedchars assert type(escape(markupchars)) == type(markupchars)
assert isinstance(escape(markupchars), basestring) assert isinstance(escape(markupchars), str)
assert htmlescape(htmlescape(markupchars)) == quotedchars assert htmlescape(htmlescape(markupchars)) == quotedchars
# Now try to pass bytes...
try:
escape(markupbytes)
assert 0
except TypeError:
pass
# ...and now not a string at all
try: try:
escape(1) escape(1)
assert 0 assert 0
except TypeError: pass except TypeError:
pass
def check_cmp(self): def check_cmp(self):
s = htmltext("foo") s = htmltext("foo")
@ -158,39 +173,27 @@ class HTMLTextTest (UTest):
except TypeError: pass except TypeError: pass
def check_format(self): def check_format(self):
s_fmt = htmltext('%s') u_fmt = htmltext('%s')
u_fmt = htmltext(u'%s') assert u_fmt % 'fooble' == "fooble"
assert s_fmt % 'foo' == "foo" assert isinstance(u_fmt % 'wibblefoo', htmltext)
assert u_fmt % 'foo' == u"foo"
assert isinstance(s_fmt % 'foo', htmltext)
assert isinstance(u_fmt % 'foo', htmltext)
assert s_fmt % markupchars == quotedchars
assert u_fmt % markupchars == quotedchars assert u_fmt % markupchars == quotedchars
assert s_fmt % None == "None"
assert u_fmt % None == "None" assert u_fmt % None == "None"
assert s_fmt % unicodechars == unicodechars
assert u_fmt % unicodechars == unicodechars assert u_fmt % unicodechars == unicodechars
assert s_fmt % htmltext(unicodechars) == unicodechars
assert u_fmt % htmltext(unicodechars) == unicodechars assert u_fmt % htmltext(unicodechars) == unicodechars
assert htmltext('%r') % Wrapper(markupchars) == quotedchars assert htmltext('%r') % Wrapper(markupchars) == quotedchars
assert htmltext('%r') % unicodechars == `unicodechars` assert htmltext('%r') % unicodechars == repr(unicodechars)
assert htmltext('%s%s') % ('foo', htmltext(markupchars)) \ assert htmltext('%s%s') % ('foo', htmltext(markupchars)) \
== ("foo" + markupchars) == ("foo" + markupchars)
assert htmltext('%d') % 10 == "10" assert htmltext('%d') % 10 == "10"
assert htmltext('%.1f') % 10 == "10.0" assert htmltext('%.1f') % 10 == "10.0"
try:
s_fmt % Broken()
assert 0
except BrokenError: pass
try: try:
htmltext('%r') % Broken() htmltext('%r') % Broken()
assert 0 assert 0
except BrokenError: pass except BrokenError: pass
try:
s_fmt % (1, 2) assert htmltext('%d') % 12300000000000000000 == "12300000000000000000"
assert 0
except TypeError: pass
assert htmltext('%d') % 12300000000000000000L == "12300000000000000000"
def check_dict_format(self): def check_dict_format(self):
args = {'a': 'foo&', 'b': htmltext('bar&')} args = {'a': 'foo&', 'b': htmltext('bar&')}
@ -232,8 +235,8 @@ class HTMLTextTest (UTest):
assert htmltext(' ').join([htmltext(markupchars), 'bar']) == \ assert htmltext(' ').join([htmltext(markupchars), 'bar']) == \
markupchars + " bar" markupchars + " bar"
assert isinstance(htmltext('').join([]), htmltext) assert isinstance(htmltext('').join([]), htmltext)
assert htmltext(u' ').join([unicodechars]) == unicodechars assert htmltext(' ').join([unicodechars]) == unicodechars
assert htmltext(u' ').join(['']) == u'' assert htmltext(' ').join(['']) == ''
try: try:
htmltext('').join(1) htmltext('').join(1)
assert 0 assert 0
@ -307,8 +310,8 @@ class TemplateTest (UTest):
assert t.getvalue() == 'abcd' assert t.getvalue() == 'abcd'
t += 123 t += 123
assert t.getvalue() == 'abcd123' assert t.getvalue() == 'abcd123'
t += u'\u1234' t += '\\u1234'
assert t.getvalue() == u'abcd123\u1234' assert t.getvalue() == 'abcd123\\u1234'
try: try:
t += Broken(); t.getvalue() t += Broken(); t.getvalue()
assert 0 assert 0

View File

@ -8,9 +8,9 @@ import re
import string import string
import os import os
import tempfile import tempfile
import urllib import urllib.request, urllib.parse, urllib.error
import rfc822 import email
from StringIO import StringIO import io
import quixote import quixote
from quixote.http_response import HTTPResponse from quixote.http_response import HTTPResponse
@ -48,10 +48,6 @@ def get_content_type(environ):
return None return None
def _decode_string(s, charset): def _decode_string(s, charset):
if charset == 'iso-8859-1' == quixote.DEFAULT_CHARSET:
# To avoid breaking applications that are not Unicode-safe, return
# a str instance in this case.
return s
try: try:
return s.decode(charset) return s.decode(charset)
except LookupError: except LookupError:
@ -65,7 +61,9 @@ def parse_header(line):
Return the main content-type and a dictionary of options. Return the main content-type and a dictionary of options.
""" """
plist = map(lambda x: x.strip(), line.split(';')) if isinstance(line, email.header.Header): # file upload
line = ''.join(val for val, charset in line._chunks)
plist = [val.strip() for val in line.split(';')]
key = plist.pop(0).lower() key = plist.pop(0).lower()
pdict = {} pdict = {}
for p in plist: for p in plist:
@ -100,8 +98,8 @@ def parse_query(qs, charset):
value = '' value = ''
else: else:
name, value = chunk.split('=', 1) name, value = chunk.split('=', 1)
name = urllib.unquote(name.replace('+', ' ')) name = urllib.parse.unquote_plus(name)
value = urllib.unquote(value.replace('+', ' ')) value = urllib.parse.unquote_plus(value)
name = _decode_string(name, charset) name = _decode_string(name, charset)
value = _decode_string(value, charset) value = _decode_string(value, charset)
_add_field_value(fields, name, value) _add_field_value(fields, name, value)
@ -205,7 +203,7 @@ class HTTPRequest:
# middleware expect that. We cannot rely on the application to # middleware expect that. We cannot rely on the application to
# read it completely (e.g. if there is some PublishError raised). # read it completely (e.g. if there is some PublishError raised).
if length < 20000: if length < 20000:
fp = StringIO() fp = io.BytesIO()
else: else:
fp = tempfile.TemporaryFile("w+b") fp = tempfile.TemporaryFile("w+b")
remaining = length remaining = length
@ -226,6 +224,8 @@ class HTTPRequest:
# Use the declared charset if it's provided (most browser's don't # Use the declared charset if it's provided (most browser's don't
# provide it to avoid breaking old HTTP servers). # provide it to avoid breaking old HTTP servers).
charset = params.get('charset', self.charset) charset = params.get('charset', self.charset)
# should contain only ASCII characters but parse as iso-8859-1
query = query.decode('iso-8859-1')
self.form.update(parse_query(query, charset)) self.form.update(parse_query(query, charset))
def _process_multipart(self, length, params): def _process_multipart(self, length, params):
@ -243,14 +243,14 @@ class HTTPRequest:
raise RequestError('unexpected end of multipart/form-data') raise RequestError('unexpected end of multipart/form-data')
def _process_multipart_body(self, mimeinput, charset): def _process_multipart_body(self, mimeinput, charset):
headers = StringIO() headers = io.BytesIO()
lines = mimeinput.readpart() lines = mimeinput.readpart()
for line in lines: for line in lines:
headers.write(line) headers.write(line)
if line == '\r\n': if line == b'\r\n':
break break
headers.seek(0) headers.seek(0)
headers = rfc822.Message(headers) headers = email.message_from_binary_file(headers)
ctype, ctype_params = parse_header(headers.get('content-type', '')) ctype, ctype_params = parse_header(headers.get('content-type', ''))
if ctype and 'charset' in ctype_params: if ctype and 'charset' in ctype_params:
charset = ctype_params['charset'] charset = ctype_params['charset']
@ -272,7 +272,7 @@ class HTTPRequest:
upload.receive(lines) upload.receive(lines)
_add_field_value(self.form, name, upload) _add_field_value(self.form, name, upload)
else: else:
value = _decode_string(''.join(lines), charset or self.charset) value = _decode_string(b''.join(lines), charset or self.charset)
_add_field_value(self.form, name, value) _add_field_value(self.form, name, value)
def get_header(self, name, default=None): def get_header(self, name, default=None):
@ -408,7 +408,7 @@ class HTTPRequest:
any). any).
""" """
return "%s://%s%s" % (self.get_scheme(), self.get_server(), return "%s://%s%s" % (self.get_scheme(), self.get_server(),
urllib.quote(self.get_path(n))) urllib.parse.quote(self.get_path(n)))
def get_environ(self, key, default=None): def get_environ(self, key, default=None):
"""get_environ(key : string) -> string """get_environ(key : string) -> string
@ -616,7 +616,7 @@ def parse_cookies(text):
result[name] = value result[name] = value
return result return result
SAFE_CHARS = string.letters + string.digits + "-@&+=_., " SAFE_CHARS = string.ascii_letters + string.digits + "-@&+=_., "
_safe_trans = None _safe_trans = None
def make_safe_filename(s): def make_safe_filename(s):
@ -737,7 +737,7 @@ class LineInput:
def __init__(self, fp, length): def __init__(self, fp, length):
self.fp = fp self.fp = fp
self.length = length self.length = length
self.buf = '' self.buf = b''
def readline(self, maxlength=4096): def readline(self, maxlength=4096):
# fill buffer # fill buffer
@ -751,17 +751,17 @@ class LineInput:
self.buf += chunk self.buf += chunk
# split into lines # split into lines
buf = self.buf buf = self.buf
i = buf.find('\r\n') i = buf.find(b'\r\n')
if i >= 0: if i >= 0:
i += 2 i += 2
self.buf = buf[i:] self.buf = buf[i:]
return buf[:i] return buf[:i]
elif buf.endswith('\r'): elif buf.endswith(b'\r'):
# avoid splitting CR LF pairs # avoid splitting CR LF pairs
self.buf = '\r' self.buf = b'\r'
return buf[:-1] return buf[:-1]
else: else:
self.buf = '' self.buf = b''
return buf return buf
class MIMEInput: class MIMEInput:
@ -772,7 +772,7 @@ class MIMEInput:
def __init__(self, fp, boundary, length): def __init__(self, fp, boundary, length):
self.lineinput = LineInput(fp, length) self.lineinput = LineInput(fp, length)
self.pat = re.compile(r'--%s(--)?' % re.escape(boundary)) self.pat = b'--' + boundary.encode('iso-8859-1')
self.done = False self.done = False
def moreparts(self): def moreparts(self):
@ -783,20 +783,20 @@ class MIMEInput:
"""Generate all the lines up to a MIME boundary. Note that you """Generate all the lines up to a MIME boundary. Note that you
must exhaust the generator before calling this function again.""" must exhaust the generator before calling this function again."""
assert not self.done assert not self.done
last_line = '' last_line = b''
while 1: while 1:
line = self.lineinput.readline() line = self.lineinput.readline()
if not line: if not line:
# Hit EOF -- nothing more to read. This should *not* happen # Hit EOF -- nothing more to read. This should *not* happen
# in a well-formed MIME message. # in a well-formed MIME message.
raise EOFError('MIME boundary not found (end of input)') raise EOFError('MIME boundary not found (end of input)')
if last_line.endswith('\r\n') or last_line == '': # FIXME: check this
m = self.pat.match(line) if last_line.endswith(b'\r\n') or last_line == b'':
if m: if line.startswith(self.pat):
# If we hit the boundary line, return now. Forget # If we hit the boundary line, return now. Forget
# the current line *and* the CRLF ending of the # the current line *and* the CRLF ending of the
# previous line. # previous line.
if m.group(1): if line.startswith(self.pat + b'--'):
# hit final boundary # hit final boundary
self.done = True self.done = True
yield last_line[:-2] yield last_line[:-2]

View File

@ -249,12 +249,12 @@ class HTTPResponse:
return chunk return chunk
def _compress_body(self, body): def _compress_body(self, body):
"""(body: str) -> str """(body: bytes) -> bytes
""" """
n = len(body) n = len(body)
compressed_body = ''.join(self._generate_compressed([body])) compressed_body = b''.join(self._generate_compressed([body]))
ratio = float(n) / len(compressed_body) ratio = float(n) / len(compressed_body)
#print "gzip original size %d, ratio %.1f" % (n, ratio) #print("gzip original size %d, ratio %.1f" % (n, ratio))
if ratio > 1.0: if ratio > 1.0:
# only compress if we save space # only compress if we save space
self.set_header("Content-Encoding", "gzip") self.set_header("Content-Encoding", "gzip")
@ -509,8 +509,8 @@ class HTTPResponse:
# The stream is terminated by a zero length chunk. # The stream is terminated by a zero length chunk.
for chunk in stream: for chunk in stream:
if chunk: if chunk:
yield ''.join(['%x\r\n' % len(chunk), chunk, '\r\n']) yield b''.join([('%x\r\n' % len(chunk)).encode(), chunk, b'\r\n'])
yield '0\r\n\r\n' yield b'0\r\n\r\n'
def generate_body_chunks(self): def generate_body_chunks(self):
stream = self._generate_encoded_body() stream = self._generate_encoded_body()
@ -534,11 +534,13 @@ class HTTPResponse:
flush_output = not self.buffered and hasattr(output, 'flush') flush_output = not self.buffered and hasattr(output, 'flush')
if include_status: if include_status:
# "Status" header must come first. # "Status" header must come first.
output.write("Status: %03d %s\r\n" % (self.status_code, s = 'Status: %03d %s\r\n' % (self.status_code, self.reason_phrase)
self.reason_phrase)) output.write(s.encode('utf-8'))
for name, value in self.generate_headers(): for name, value in self.generate_headers():
output.write("%s: %s\r\n" % (name, value)) s = "%s: %s\r\n" % (name, value))
output.write("\r\n") output.write(s.encode('utf-8'))
output.write(b"\r\n")
if flush_output: if flush_output:
output.flush() output.flush()
if not include_body: if not include_body:

View File

@ -1,9 +1,9 @@
"""Logic for publishing modules and objects on the Web. """Logic for publishing modules and objects on the Web.
""" """
import sys, traceback, StringIO import sys, traceback, io
import time import time
import urlparse import urllib.parse
import cgitb import cgitb
from quixote.errors import PublishError, MethodNotAllowedError, \ from quixote.errors import PublishError, MethodNotAllowedError, \
@ -186,7 +186,7 @@ class Publisher:
def _generate_plaintext_error(self, request, original_response, def _generate_plaintext_error(self, request, original_response,
exc_type, exc_value, tb): exc_type, exc_value, tb):
error_file = StringIO.StringIO() error_file = io.StringIO()
# format the traceback # format the traceback
traceback.print_exception(exc_type, exc_value, tb, file=error_file) traceback.print_exception(exc_type, exc_value, tb, file=error_file)
@ -201,14 +201,23 @@ class Publisher:
def _generate_cgitb_error(self, request, original_response, def _generate_cgitb_error(self, request, original_response,
exc_type, exc_value, tb): exc_type, exc_value, tb):
error_file = StringIO.StringIO() # let cgitb.Hook have the type it wants...
error_file = io.StringIO()
hook = cgitb.Hook(file=error_file) hook = cgitb.Hook(file=error_file)
hook(exc_type, exc_value, tb) hook(exc_type, exc_value, tb)
error_file.write('<h2>Original Request</h2>')
error_file.write(str(util.dump_request(request))) byte_error_file = io.BytesIO()
error_file.write('<h2>Original Response</h2><pre>') byte_error_file.write(b'<h2>Original Request</h2>')
original_response.write(error_file) # dump_request returns an HTMLText object
error_file.write('</pre>') s = str(util.dump_request(request))
byte_error_file.write(s.encode('latin-1', 'strict'))
byte_error_file.write(b'<h2>Original Response</h2><pre>')
original_response.write(byte_error_file)
byte_error_file.write(b'</pre>')
# Now we push the bytes to the "real" error file...
s = byte_error_file.getvalue().decode('latin-1')
error_file.write(s)
return error_file.getvalue() return error_file.getvalue()
@ -332,7 +341,7 @@ def redirect(location, permanent=False):
not honor the redirect). not honor the redirect).
""" """
request = _publisher.get_request() request = _publisher.get_request()
location = urlparse.urljoin(request.get_url(), str(location)) location = urllib.parse.urljoin(request.get_url(), str(location))
return request.response.redirect(location, permanent) return request.response.redirect(location, permanent)
def get_session(): def get_session():

View File

@ -1,9 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python3
"""An HTTP handler for Medusa that publishes a Quixote application. """An HTTP handler for Medusa that publishes a Quixote application.
""" """
import asyncore, rfc822, socket, urllib import asyncore
from StringIO import StringIO import email
import socket, urllib.request, urllib.parse, urllib.error
from io import StringIO
from medusa import http_server, xmlrpc_handler from medusa import http_server, xmlrpc_handler
import quixote import quixote
@ -14,7 +16,7 @@ class StreamProducer:
def more(self): def more(self):
try: try:
return self.chunks.next() return next(self.chunks)
except StopIteration: except StopIteration:
return '' return ''
@ -29,7 +31,7 @@ class QuixoteHandler:
return True return True
def handle_request(self, request): def handle_request(self, request):
msg = rfc822.Message(StringIO('\n'.join(request.header))) msg = email.Message(StringIO('\n'.join(request.header)))
length = int(msg.get('Content-Length', '0')) length = int(msg.get('Content-Length', '0'))
if length: if length:
request.collector = xmlrpc_handler.collector(self, request) request.collector = xmlrpc_handler.collector(self, request)
@ -37,7 +39,7 @@ class QuixoteHandler:
self.continue_request('', request) self.continue_request('', request)
def continue_request(self, data, request): def continue_request(self, data, request):
msg = rfc822.Message(StringIO('\n'.join(request.header))) msg = email.Message(StringIO('\n'.join(request.header)))
remote_addr, remote_port = request.channel.addr remote_addr, remote_port = request.channel.addr
if '#' in request.uri: if '#' in request.uri:
# MSIE is buggy and sometimes includes fragments in URLs # MSIE is buggy and sometimes includes fragments in URLs
@ -48,7 +50,7 @@ class QuixoteHandler:
path = request.uri path = request.uri
query_string = '' query_string = ''
path = urllib.unquote(path) path = urllib.parse.unquote(path)
server_port = str(self.server.port) server_port = str(self.server.port)
http_host = msg.get("Host") http_host = msg.get("Host")
if http_host: if http_host:

View File

@ -11,8 +11,8 @@ class QuixoteHandler(scgi_server.SCGIHandler):
self.script_name = script_name self.script_name = script_name
def handle_connection(self, conn): def handle_connection(self, conn):
input = conn.makefile("r") input = conn.makefile("rb")
output = conn.makefile("w") output = conn.makefile("wb")
env = self.read_env(input) env = self.read_env(input)
if self.script_name is not None: if self.script_name is not None:

View File

@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3
"""A simple, single threaded, synchronous HTTP server. """A simple, single threaded, synchronous HTTP server.
""" """
import sys import sys
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib import urllib.request, urllib.parse, urllib.error
import quixote import quixote
from quixote import get_publisher from quixote import get_publisher
from quixote.util import import_object from quixote.util import import_object
@ -28,12 +28,9 @@ class HTTPRequestHandler(BaseHTTPRequestHandler):
env['PATH_INFO'], env['QUERY_STRING'] = self.path.split('?', 1) env['PATH_INFO'], env['QUERY_STRING'] = self.path.split('?', 1)
else: else:
env['PATH_INFO'] = self.path env['PATH_INFO'] = self.path
env['PATH_INFO'] = urllib.unquote(env['PATH_INFO']) env['PATH_INFO'] = urllib.parse.unquote(env['PATH_INFO'])
if self.headers.typeheader is None: env['CONTENT_TYPE'] = self.headers.get('content-type')
env['CONTENT_TYPE'] = self.headers.type env['CONTENT_LENGTH'] = self.headers.get('content-length') or "0"
else:
env['CONTENT_TYPE'] = self.headers.typeheader
env['CONTENT_LENGTH'] = self.headers.getheader('content-length') or "0"
for name, value in self.headers.items(): for name, value in self.headers.items():
header_name = 'HTTP_' + name.upper().replace('-', '_') header_name = 'HTTP_' + name.upper().replace('-', '_')
env[header_name] = value env[header_name] = value
@ -56,12 +53,11 @@ class HTTPRequestHandler(BaseHTTPRequestHandler):
# single threaded server, persistent connections will block others # single threaded server, persistent connections will block others
response.set_header('connection', 'close') response.set_header('connection', 'close')
try: try:
self.send_response(response.get_status_code(), self.send_response(response.get_status_code(), response.get_reason_phrase())
response.get_reason_phrase()) self.flush_headers()
response.write(self.wfile, include_status=False, response.write(self.wfile, include_status=False, include_body=include_body)
include_body=include_body) except IOError as err:
except IOError, err: print("IOError while sending response ignored: %s" % err)
print "IOError while sending response ignored: %s" % err
def do_POST(self): def do_POST(self):
return self.process(self.get_cgi_env('POST')) return self.process(self.get_cgi_env('POST'))
@ -78,14 +74,7 @@ class HTTPRequestHandler(BaseHTTPRequestHandler):
that adds the 'Date' header is removed to avoid duplicating the one that adds the 'Date' header is removed to avoid duplicating the one
that Quixote adds and the log_request() call has been removed. that Quixote adds and the log_request() call has been removed.
""" """
if message is None: self.send_response_only(code, message)
if code in self.responses:
message = self.responses[code][0]
else:
message = ''
if self.request_version != 'HTTP/0.9':
self.wfile.write("%s %d %s\r\n" %
(self.protocol_version, code, message))
self.send_header('Server', self.version_string()) self.send_header('Server', self.version_string())
def run(create_publisher, host='', port=80, https=False): def run(create_publisher, host='', port=80, https=False):

View File

@ -2,8 +2,9 @@
"""An HTTP server for Twisted that publishes a Quixote application. """An HTTP server for Twisted that publishes a Quixote application.
""" """
import urllib import urllib.request, urllib.parse, urllib.error
from twisted.web import http, server from twisted.protocols import http
from twisted.web import server
from twisted.python import threadable from twisted.python import threadable
from twisted.internet import reactor from twisted.internet import reactor
@ -45,7 +46,7 @@ class QuixoteRequest(server.Request):
""" """
# Twisted doesn't decode the path for us, so let's do it here. # Twisted doesn't decode the path for us, so let's do it here.
if '%' in self.path: if '%' in self.path:
self.path = urllib.unquote(self.path) self.path = urllib.parse.unquote(self.path)
serverName = self.getRequestHostname().split(':')[0] serverName = self.getRequestHostname().split(':')[0]
env = {"SERVER_SOFTWARE": server.version, env = {"SERVER_SOFTWARE": server.version,
@ -109,7 +110,7 @@ class QuixoteProducer:
def resumeProducing(self): def resumeProducing(self):
if self.request: if self.request:
try: try:
chunk = self.stream.next() chunk = next(self.stream)
except StopIteration: except StopIteration:
self.request.unregisterProducer() self.request.unregisterProducer()
self.request.finish() self.request.finish()

View File

@ -18,9 +18,10 @@ import os
import time import time
import base64 import base64
import mimetypes import mimetypes
import urllib import urllib.request, urllib.parse, urllib.error
import xmlrpclib import xmlrpc.client
from rfc822 import formatdate from email.utils import formatdate
import quixote import quixote
from quixote import errors from quixote import errors
from quixote.directory import Directory from quixote.directory import Directory
@ -68,20 +69,20 @@ def xmlrpc(request, func):
data = request.stdin.read(length) data = request.stdin.read(length)
# Parse arguments # Parse arguments
params, method = xmlrpclib.loads(data) params, method = xmlrpc.client.loads(data)
try: try:
result = func(method, params) result = func(method, params)
except xmlrpclib.Fault, exc: except xmlrpc.client.Fault as exc:
result = exc result = exc
except: except:
# report exception back to client # report exception back to client
result = xmlrpclib.dumps( result = xmlrpc.client.dumps(
xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)) xmlrpc.client.Fault(1, "%s:%s" % (sys.exc_info()[0], sys.exc_info()[1]))
) )
else: else:
result = (result,) result = (result,)
result = xmlrpclib.dumps(result, methodresponse=1) result = xmlrpc.client.dumps(result, methodresponse=1)
request.response.set_content_type('text/xml') request.response.set_content_type('text/xml')
return result return result
@ -98,7 +99,7 @@ class FileStream(Stream):
def __iter__(self): def __iter__(self):
return self return self
def next(self): def __next__(self):
chunk = self.fp.read(self.CHUNK_SIZE) chunk = self.fp.read(self.CHUNK_SIZE)
if not chunk: if not chunk:
raise StopIteration raise StopIteration
@ -249,7 +250,7 @@ class StaticDirectory(Directory):
for filename in files: for filename in files:
filepath = os.path.join(self.path, filename) filepath = os.path.join(self.path, filename)
marker = os.path.isdir(filepath) and "/" or "" marker = os.path.isdir(filepath) and "/" or ""
r += template % (urllib.quote(filename), filename, marker) r += template % (urllib.parse.quote(filename), filename, marker)
r += htmltext('</pre>') r += htmltext('</pre>')
body = r.getvalue() body = r.getvalue()
else: else:

View File

@ -9,10 +9,8 @@ To create an application object, execute
Authors: Mike Orr <mso@oz.net> and Titus Brown <titus@caltech.edu>. Authors: Mike Orr <mso@oz.net> and Titus Brown <titus@caltech.edu>.
Last updated 2005-05-03. Last updated 2005-05-03.
""" """
import sys
from http_request import HTTPRequest from .http_request import HTTPRequest
from StringIO import StringIO
###### QWIP: WSGI COMPATIBILITY WRAPPER FOR QUIXOTE ##################### ###### QWIP: WSGI COMPATIBILITY WRAPPER FOR QUIXOTE #####################

View File

@ -2,10 +2,9 @@ import sys, subprocess
import quixote import quixote
from quixote.server.simple_server import run from quixote.server.simple_server import run
from StringIO import StringIO from io import StringIO
import os import os
import socket import urllib.request, urllib.parse, urllib.error
import urllib
_server_url = None _server_url = None
@ -74,7 +73,7 @@ def kill_server():
global _server_url global _server_url
if _server_url != None: if _server_url != None:
try: try:
fp = urllib.urlopen('%sexit' % (_server_url,)) fp = urllib.request.urlopen('%sexit' % (_server_url,))
except: except:
pass pass