Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 38a66ad

Browse files
committed
Issue #4718: Adapt the wsgiref package so that it actually works with Python 3.x,
in accordance with http://www.wsgi.org/wsgi/Amendments_1.0
1 parent ffe431d commit 38a66ad

8 files changed

Lines changed: 184 additions & 60 deletions

File tree

Doc/library/wsgiref.rst

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,13 @@ parameter expect a WSGI-compliant dictionary to be supplied; please see
122122
def simple_app(environ, start_response):
123123
setup_testing_defaults(environ)
124124

125-
status = '200 OK'
126-
headers = [('Content-type', 'text/plain')]
125+
status = b'200 OK'
126+
headers = [(b'Content-type', b'text/plain; charset=utf-8')]
127127

128128
start_response(status, headers)
129129

130-
ret = ["%s: %s\n" % (key, value)
131-
for key, value in environ.iteritems()]
130+
ret = [("%s: %s\n" % (key, value)).encode("utf-8")
131+
for key, value in environ.items()]
132132
return ret
133133

134134
httpd = make_server('', 8000, simple_app)
@@ -161,7 +161,7 @@ also provides these miscellaneous utilities:
161161

162162
Example usage::
163163

164-
from StringIO import StringIO
164+
from io import StringIO
165165
from wsgiref.util import FileWrapper
166166

167167
# We're using a StringIO-buffer for as the file-like object
@@ -416,13 +416,13 @@ Paste" library.
416416
# Our callable object which is intentionally not compliant to the
417417
# standard, so the validator is going to break
418418
def simple_app(environ, start_response):
419-
status = '200 OK' # HTTP Status
420-
headers = [('Content-type', 'text/plain')] # HTTP Headers
419+
status = b'200 OK' # HTTP Status
420+
headers = [(b'Content-type', b'text/plain')] # HTTP Headers
421421
start_response(status, headers)
422422

423423
# This is going to break because we need to return a list, and
424424
# the validator is going to inform us
425-
return "Hello World"
425+
return b"Hello World"
426426

427427
# This is the application wrapped in a validator
428428
validator_app = validator(simple_app)
@@ -509,7 +509,7 @@ input, output, and error streams.
509509

510510
.. method:: BaseHandler._write(data)
511511

512-
Buffer the string *data* for transmission to the client. It's okay if this
512+
Buffer the bytes *data* for transmission to the client. It's okay if this
513513
method actually transmits the data; :class:`BaseHandler` just separates write
514514
and flush operations for greater efficiency when the underlying system actually
515515
has such a distinction.
@@ -712,12 +712,12 @@ This is a working "Hello World" WSGI application::
712712
# is a dictionary containing CGI-style envrironment variables and the
713713
# second variable is the callable object (see PEP333)
714714
def hello_world_app(environ, start_response):
715-
status = '200 OK' # HTTP Status
716-
headers = [('Content-type', 'text/plain')] # HTTP Headers
715+
status = b'200 OK' # HTTP Status
716+
headers = [(b'Content-type', b'text/plain; charset=utf-8')] # HTTP Headers
717717
start_response(status, headers)
718718

719719
# The returned object is going to be printed
720-
return ["Hello World"]
720+
return [b"Hello World"]
721721

722722
httpd = make_server('', 8000, hello_world_app)
723723
print("Serving on port 8000...")

Lib/test/test_wsgiref.py

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def hello_app(environ,start_response):
5050
def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
5151
server = make_server("", 80, app, MockServer, MockHandler)
5252
inp = BufferedReader(BytesIO(data))
53-
out = StringIO()
53+
out = BytesIO()
5454
olderr = sys.stderr
5555
err = sys.stderr = StringIO()
5656

@@ -128,13 +128,13 @@ class IntegrationTests(TestCase):
128128

129129
def check_hello(self, out, has_length=True):
130130
self.assertEqual(out,
131-
"HTTP/1.0 200 OK\r\n"
131+
("HTTP/1.0 200 OK\r\n"
132132
"Server: WSGIServer/0.1 Python/"+sys.version.split()[0]+"\r\n"
133133
"Content-Type: text/plain\r\n"
134134
"Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" +
135135
(has_length and "Content-Length: 13\r\n" or "") +
136136
"\r\n"
137-
"Hello, world!"
137+
"Hello, world!").encode("iso-8859-1")
138138
)
139139

140140
def test_plain_hello(self):
@@ -152,15 +152,44 @@ def bad_app(environ,start_response):
152152
return ["Hello, world!"]
153153
out, err = run_amock(validator(bad_app))
154154
self.failUnless(out.endswith(
155-
"A server error occurred. Please contact the administrator."
155+
b"A server error occurred. Please contact the administrator."
156156
))
157157
self.assertEqual(
158158
err.splitlines()[-2],
159159
"AssertionError: Headers (('Content-Type', 'text/plain')) must"
160160
" be of type list: <class 'tuple'>"
161161
)
162162

163+
def test_wsgi_input(self):
164+
def bad_app(e,s):
165+
e["wsgi.input"].read()
166+
s(b"200 OK", [(b"Content-Type", b"text/plain; charset=utf-8")])
167+
return [b"data"]
168+
out, err = run_amock(validator(bad_app))
169+
self.failUnless(out.endswith(
170+
b"A server error occurred. Please contact the administrator."
171+
))
172+
self.assertEqual(
173+
err.splitlines()[-2], "AssertionError"
174+
)
163175

176+
def test_bytes_validation(self):
177+
def app(e, s):
178+
s(b"200 OK", [
179+
(b"Content-Type", b"text/plain; charset=utf-8"),
180+
("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
181+
])
182+
return [b"data"]
183+
out, err = run_amock(validator(app))
184+
self.failUnless(err.endswith('"GET / HTTP/1.0" 200 4\n'))
185+
self.assertEqual(
186+
b"HTTP/1.0 200 OK\r\n"
187+
b"Server: WSGIServer/0.1 Python/3.1a0\r\n"
188+
b"Content-Type: text/plain; charset=utf-8\r\n"
189+
b"Date: Wed, 24 Dec 2008 13:29:32 GMT\r\n"
190+
b"\r\n"
191+
b"data",
192+
out)
164193

165194

166195

@@ -181,6 +210,8 @@ def checkDefault(self, key, value, alt=None):
181210
util.setup_testing_defaults(env)
182211
if isinstance(value,StringIO):
183212
self.failUnless(isinstance(env[key],StringIO))
213+
elif isinstance(value,BytesIO):
214+
self.failUnless(isinstance(env[key],BytesIO))
184215
else:
185216
self.assertEqual(env[key],value)
186217

@@ -260,7 +291,7 @@ def testDefaults(self):
260291
('wsgi.run_once', 0),
261292
('wsgi.multithread', 0),
262293
('wsgi.multiprocess', 0),
263-
('wsgi.input', StringIO("")),
294+
('wsgi.input', BytesIO()),
264295
('wsgi.errors', StringIO()),
265296
('wsgi.url_scheme','http'),
266297
]:
@@ -386,14 +417,31 @@ def testExtras(self):
386417
'\r\n'
387418
)
388419

420+
def testBytes(self):
421+
h = Headers([
422+
(b"Content-Type", b"text/plain; charset=utf-8"),
423+
])
424+
self.assertEqual("text/plain; charset=utf-8", h.get("Content-Type"))
425+
426+
h[b"Foo"] = bytes(b"bar")
427+
self.assertEqual("bar", h.get("Foo"))
428+
429+
h.setdefault(b"Bar", b"foo")
430+
self.assertEqual("foo", h.get("Bar"))
431+
432+
h.add_header(b'content-disposition', b'attachment',
433+
filename=b'bud.gif')
434+
self.assertEqual('attachment; filename="bud.gif"',
435+
h.get("content-disposition"))
436+
389437

390438
class ErrorHandler(BaseCGIHandler):
391439
"""Simple handler subclass for testing BaseHandler"""
392440

393441
def __init__(self,**kw):
394442
setup_testing_defaults(kw)
395443
BaseCGIHandler.__init__(
396-
self, StringIO(''), StringIO(), StringIO(), kw,
444+
self, BytesIO(), BytesIO(), StringIO(), kw,
397445
multithread=True, multiprocess=True
398446
)
399447

@@ -474,21 +522,32 @@ def trivial_app2(e,s):
474522
s('200 OK',[])(e['wsgi.url_scheme'])
475523
return []
476524

525+
def trivial_app3(e,s):
526+
s('200 OK',[])
527+
return ['\u0442\u0435\u0441\u0442'.encode("utf-8")]
528+
477529
h = TestHandler()
478530
h.run(trivial_app1)
479531
self.assertEqual(h.stdout.getvalue(),
480-
"Status: 200 OK\r\n"
532+
("Status: 200 OK\r\n"
481533
"Content-Length: 4\r\n"
482534
"\r\n"
483-
"http")
535+
"http").encode("iso-8859-1"))
484536

485537
h = TestHandler()
486538
h.run(trivial_app2)
487539
self.assertEqual(h.stdout.getvalue(),
488-
"Status: 200 OK\r\n"
540+
("Status: 200 OK\r\n"
489541
"\r\n"
490-
"http")
542+
"http").encode("iso-8859-1"))
491543

544+
h = TestHandler()
545+
h.run(trivial_app3)
546+
self.assertEqual(h.stdout.getvalue(),
547+
b'Status: 200 OK\r\n'
548+
b'Content-Length: 8\r\n'
549+
b'\r\n'
550+
b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
492551

493552

494553

@@ -507,18 +566,19 @@ def error_app(e,s):
507566
h = ErrorHandler()
508567
h.run(non_error_app)
509568
self.assertEqual(h.stdout.getvalue(),
510-
"Status: 200 OK\r\n"
569+
("Status: 200 OK\r\n"
511570
"Content-Length: 0\r\n"
512-
"\r\n")
571+
"\r\n").encode("iso-8859-1"))
513572
self.assertEqual(h.stderr.getvalue(),"")
514573

515574
h = ErrorHandler()
516575
h.run(error_app)
517576
self.assertEqual(h.stdout.getvalue(),
518-
"Status: %s\r\n"
577+
("Status: %s\r\n"
519578
"Content-Type: text/plain\r\n"
520579
"Content-Length: %d\r\n"
521-
"\r\n%s" % (h.error_status,len(h.error_body),h.error_body))
580+
"\r\n%s" % (h.error_status,len(h.error_body),h.error_body)
581+
).encode("iso-8859-1"))
522582

523583
self.failUnless("AssertionError" in h.stderr.getvalue())
524584

@@ -531,8 +591,8 @@ def error_app(e,s):
531591
h = ErrorHandler()
532592
h.run(error_app)
533593
self.assertEqual(h.stdout.getvalue(),
534-
"Status: 200 OK\r\n"
535-
"\r\n"+MSG)
594+
("Status: 200 OK\r\n"
595+
"\r\n"+MSG).encode("iso-8859-1"))
536596
self.failUnless("AssertionError" in h.stderr.getvalue())
537597

538598

@@ -549,7 +609,7 @@ def non_error_app(e,s):
549609
)
550610
shortpat = (
551611
"Status: 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
552-
)
612+
).encode("iso-8859-1")
553613

554614
for ssw in "FooBar/1.0", None:
555615
sw = ssw and "Server: %s\r\n" % ssw or ""
@@ -570,13 +630,31 @@ def non_error_app(e,s):
570630
h.server_software = ssw
571631
h.run(non_error_app)
572632
if proto=="HTTP/0.9":
573-
self.assertEqual(h.stdout.getvalue(),"")
633+
self.assertEqual(h.stdout.getvalue(),b"")
574634
else:
575635
self.failUnless(
576-
re.match(stdpat%(version,sw), h.stdout.getvalue()),
577-
(stdpat%(version,sw), h.stdout.getvalue())
636+
re.match((stdpat%(version,sw)).encode("iso-8859-1"),
637+
h.stdout.getvalue()),
638+
((stdpat%(version,sw)).encode("iso-8859-1"),
639+
h.stdout.getvalue())
578640
)
579641

642+
def testBytesData(self):
643+
def app(e, s):
644+
s(b"200 OK", [
645+
(b"Content-Type", b"text/plain; charset=utf-8"),
646+
])
647+
return [b"data"]
648+
649+
h = TestHandler()
650+
h.run(app)
651+
self.assertEqual(b"Status: 200 OK\r\n"
652+
b"Content-Type: text/plain; charset=utf-8\r\n"
653+
b"Content-Length: 4\r\n"
654+
b"\r\n"
655+
b"data",
656+
h.stdout.getvalue())
657+
580658
# This epilogue is needed for compatibility with the Python 2.5 regrtest module
581659

582660
def test_main():

Lib/wsgiref/handlers.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,19 +157,29 @@ def start_response(self, status, headers,exc_info=None):
157157
elif self.headers is not None:
158158
raise AssertionError("Headers already set!")
159159

160-
assert type(status) is str,"Status must be a string"
160+
status = self._convert_string_type(status, "Status")
161161
assert len(status)>=4,"Status must be at least 4 characters"
162162
assert int(status[:3]),"Status message must begin w/3-digit code"
163163
assert status[3]==" ", "Status message must have a space after code"
164-
if __debug__:
165-
for name,val in headers:
166-
assert type(name) is str,"Header names must be strings"
167-
assert type(val) is str,"Header values must be strings"
168-
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
164+
165+
str_headers = []
166+
for name,val in headers:
167+
name = self._convert_string_type(name, "Header name")
168+
val = self._convert_string_type(val, "Header value")
169+
str_headers.append((name, val))
170+
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
171+
169172
self.status = status
170-
self.headers = self.headers_class(headers)
173+
self.headers = self.headers_class(str_headers)
171174
return self.write
172175

176+
def _convert_string_type(self, value, title):
177+
"""Convert/check value type."""
178+
if isinstance(value, str):
179+
return value
180+
assert isinstance(value, bytes), \
181+
"{0} must be a string or bytes object (not {1})".format(title, value)
182+
return str(value, "iso-8859-1")
173183

174184
def send_preamble(self):
175185
"""Transmit version/status/date/server, via self._write()"""
@@ -188,7 +198,8 @@ def send_preamble(self):
188198
def write(self, data):
189199
"""'write()' callable as specified by PEP 333"""
190200

191-
assert type(data) is str,"write() argument must be string"
201+
assert isinstance(data, (str, bytes)), \
202+
"write() argument must be a string or bytes"
192203

193204
if not self.status:
194205
raise AssertionError("write() before start_response()")
@@ -382,8 +393,13 @@ def add_cgi_vars(self):
382393
self.environ.update(self.base_env)
383394

384395
def _write(self,data):
396+
if isinstance(data, str):
397+
try:
398+
data = data.encode("iso-8859-1")
399+
except UnicodeEncodeError:
400+
raise ValueError("Unicode data must contain only code points"
401+
" representable in ISO-8859-1 encoding")
385402
self.stdout.write(data)
386-
self._write = self.stdout.write
387403

388404
def _flush(self):
389405
self.stdout.flush()

0 commit comments

Comments
 (0)