Index: Doc/library/wsgiref.rst =================================================================== --- Doc/library/wsgiref.rst (revision 67899) +++ Doc/library/wsgiref.rst (working copy) @@ -123,11 +123,11 @@ setup_testing_defaults(environ) status = '200 OK' - headers = [('Content-type', 'text/plain')] + headers = [('Content-type', 'text/plain; charset=utf-8')] start_response(status, headers) - ret = ["%s: %s\n" % (key, value) + ret = [("%s: %s\n" % (key, value)).encode("utf-8") for key, value in environ.iteritems()] return ret @@ -161,7 +161,7 @@ Example usage:: - from StringIO import StringIO + from io import StringIO from wsgiref.util import FileWrapper # We're using a StringIO-buffer for as the file-like object @@ -509,7 +509,7 @@ .. method:: BaseHandler._write(data) - Buffer the string *data* for transmission to the client. It's okay if this + Buffer the bytes *data* for transmission to the client. It's okay if this method actually transmits the data; :class:`BaseHandler` just separates write and flush operations for greater efficiency when the underlying system actually has such a distinction. @@ -717,7 +717,7 @@ start_response(status, headers) # The returned object is going to be printed - return ["Hello World"] + return [b"Hello World"] httpd = make_server('', 8000, hello_world_app) print("Serving on port 8000...") Index: Lib/wsgiref/util.py =================================================================== --- Lib/wsgiref/util.py (revision 67899) +++ Lib/wsgiref/util.py (working copy) @@ -149,8 +149,8 @@ environ.setdefault('wsgi.multithread', 0) environ.setdefault('wsgi.multiprocess', 0) - from io import StringIO - environ.setdefault('wsgi.input', StringIO("")) + from io import StringIO, BytesIO + environ.setdefault('wsgi.input', BytesIO()) environ.setdefault('wsgi.errors', StringIO()) environ.setdefault('wsgi.url_scheme',guess_scheme(environ)) Index: Lib/wsgiref/simple_server.py =================================================================== --- Lib/wsgiref/simple_server.py (revision 67899) +++ Lib/wsgiref/simple_server.py (working copy) @@ -111,8 +111,7 @@ if length: env['CONTENT_LENGTH'] = length - for h in self.headers: - k,v = h.split(':',1) + for k, v in self.headers.items(): k=k.replace('-','_').upper(); v=v.strip() if k in env: continue # skip content length, type,etc. @@ -168,11 +167,11 @@ stdout = StringIO() print("Hello world!", file=stdout) print(file=stdout) - h = environ.items(); h.sort() + h = sorted(environ.items()) for k,v in h: print(k,'=',repr(v), file=stdout) - start_response("200 OK", [('Content-Type','text/plain')]) - return [stdout.getvalue()] + start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) + return [stdout.getvalue().encode("utf-8")] def make_server( Index: Lib/wsgiref/handlers.py =================================================================== --- Lib/wsgiref/handlers.py (revision 67899) +++ Lib/wsgiref/handlers.py (working copy) @@ -157,38 +157,49 @@ elif self.headers is not None: raise AssertionError("Headers already set!") - assert type(status) is str,"Status must be a string" + status = self._check_type(status, "Status") assert len(status)>=4,"Status must be at least 4 characters" assert int(status[:3]),"Status message must begin w/3-digit code" assert status[3]==" ", "Status message must have a space after code" if __debug__: for name,val in headers: - assert type(name) is str,"Header names must be strings" - assert type(val) is str,"Header values must be strings" + name = self._check_type(name, "Header name") + val = self._check_type(val, "Header value") assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed" self.status = status self.headers = self.headers_class(headers) return self.write + def _check_type(self, value, title): + """Check/convert value type.""" + assert isinstance(value, (str, bytes, bytearray, memoryview)), \ + "{0} must be string or bytes object".format(title) + if isinstance(value, (bytes, bytearray)): + value = value.decode("ascii") + elif isinstance(value, memoryview): + value = value.tobytes().decode("ascii") + return value def send_preamble(self): """Transmit version/status/date/server, via self._write()""" + lines = [] if self.origin_server: if self.client_is_modern(): - self._write('HTTP/%s %s\r\n' % (self.http_version,self.status)) + lines.append('HTTP/%s %s' % (self.http_version,self.status)) if 'Date' not in self.headers: - self._write( - 'Date: %s\r\n' % format_date_time(time.time()) - ) + lines.append('Date: %s' % format_date_time(time.time())) if self.server_software and 'Server' not in self.headers: - self._write('Server: %s\r\n' % self.server_software) + lines.append('Server: %s' % self.server_software) else: - self._write('Status: %s\r\n' % self.status) + lines.append('Status: %s' % self.status) + preamble = "\r\n".join(lines) + "\r\n" + self._write(preamble.encode("ascii")) def write(self, data): """'write()' callable as specified by PEP 333""" - assert type(data) is str,"write() argument must be string" + assert isinstance(data, (bytes, bytearray, memoryview)), \ + "write() argument must be bytes object" if not self.status: raise AssertionError("write() before start_response()") @@ -253,7 +264,7 @@ self.headers_sent = True if not self.origin_server or self.client_is_modern(): self.send_preamble() - self._write(str(self.headers)) + self._write(str(self.headers).encode("ascii")) def result_is_file(self): @@ -305,7 +316,7 @@ include any here! """ start_response(self.error_status,self.error_headers[:],sys.exc_info()) - return [self.error_body] + return [self.error_body.encode("ascii")] # Pure abstract methods; *must* be overridden in subclasses Index: Lib/test/test_wsgiref.py =================================================================== --- Lib/test/test_wsgiref.py (revision 67899) +++ Lib/test/test_wsgiref.py (working copy) @@ -45,12 +45,12 @@ ('Content-Type','text/plain'), ('Date','Mon, 05 Jun 2006 18:49:54 GMT') ]) - return ["Hello, world!"] + return [b"Hello, world!"] def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"): server = make_server("", 80, app, MockServer, MockHandler) inp = BufferedReader(BytesIO(data)) - out = StringIO() + out = BytesIO() olderr = sys.stderr err = sys.stderr = StringIO() @@ -128,13 +128,13 @@ def check_hello(self, out, has_length=True): self.assertEqual(out, - "HTTP/1.0 200 OK\r\n" + ("HTTP/1.0 200 OK\r\n" "Server: WSGIServer/0.1 Python/"+sys.version.split()[0]+"\r\n" "Content-Type: text/plain\r\n" "Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" + (has_length and "Content-Length: 13\r\n" or "") + "\r\n" - "Hello, world!" + "Hello, world!").encode("ascii") ) def test_plain_hello(self): @@ -149,10 +149,10 @@ def test_simple_validation_error(self): def bad_app(environ,start_response): start_response("200 OK", ('Content-Type','text/plain')) - return ["Hello, world!"] + return [b"Hello, world!"] out, err = run_amock(validator(bad_app)) self.failUnless(out.endswith( - "A server error occurred. Please contact the administrator." + b"A server error occurred. Please contact the administrator." )) self.assertEqual( err.splitlines()[-2], @@ -179,7 +179,9 @@ # Check defaulting when empty env = {} util.setup_testing_defaults(env) - if isinstance(value,StringIO): + if isinstance(value,BytesIO): + self.failUnless(isinstance(env[key],BytesIO)) + elif isinstance(value,StringIO): self.failUnless(isinstance(env[key],StringIO)) else: self.assertEqual(env[key],value) @@ -209,7 +211,7 @@ def checkFW(self,text,size,match): def make_it(text=text,size=size): - return util.FileWrapper(StringIO(text),size) + return util.FileWrapper(BytesIO(text),size) compare_generic_iter(make_it,match) @@ -260,7 +262,7 @@ ('wsgi.run_once', 0), ('wsgi.multithread', 0), ('wsgi.multiprocess', 0), - ('wsgi.input', StringIO("")), + ('wsgi.input', BytesIO()), ('wsgi.errors', StringIO()), ('wsgi.url_scheme','http'), ]: @@ -314,7 +316,7 @@ SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni") def testFileWrapper(self): - self.checkFW("xyz"*50, 120, ["xyz"*40,"xyz"*10]) + self.checkFW(b"xyz"*50, 120, [b"xyz"*40,b"xyz"*10]) def testHopByHop(self): for hop in ( @@ -393,7 +395,7 @@ def __init__(self,**kw): setup_testing_defaults(kw) BaseCGIHandler.__init__( - self, StringIO(''), StringIO(), StringIO(), kw, + self, BytesIO(), BytesIO(), StringIO(), kw, multithread=True, multiprocess=True ) @@ -468,26 +470,26 @@ def trivial_app1(e,s): s('200 OK',[]) - return [e['wsgi.url_scheme']] + return [e['wsgi.url_scheme'].encode("ascii")] def trivial_app2(e,s): - s('200 OK',[])(e['wsgi.url_scheme']) + s('200 OK',[])(e['wsgi.url_scheme'].encode("ascii")) return [] h = TestHandler() h.run(trivial_app1) self.assertEqual(h.stdout.getvalue(), - "Status: 200 OK\r\n" + ("Status: 200 OK\r\n" "Content-Length: 4\r\n" "\r\n" - "http") + "http").encode("ascii")) h = TestHandler() h.run(trivial_app2) self.assertEqual(h.stdout.getvalue(), - "Status: 200 OK\r\n" + ("Status: 200 OK\r\n" "\r\n" - "http") + "http").encode("ascii")) @@ -507,32 +509,33 @@ h = ErrorHandler() h.run(non_error_app) self.assertEqual(h.stdout.getvalue(), - "Status: 200 OK\r\n" + ("Status: 200 OK\r\n" "Content-Length: 0\r\n" - "\r\n") + "\r\n").encode("ascii")) self.assertEqual(h.stderr.getvalue(),"") h = ErrorHandler() h.run(error_app) self.assertEqual(h.stdout.getvalue(), - "Status: %s\r\n" + ("Status: %s\r\n" "Content-Type: text/plain\r\n" "Content-Length: %d\r\n" - "\r\n%s" % (h.error_status,len(h.error_body),h.error_body)) + "\r\n%s" % (h.error_status,len(h.error_body),h.error_body) + ).encode("ascii")) self.failUnless("AssertionError" in h.stderr.getvalue()) def testErrorAfterOutput(self): MSG = "Some output has been sent" def error_app(e,s): - s("200 OK",[])(MSG) + s("200 OK",[])(MSG.encode("ascii")) raise AssertionError("This should be caught by handler") h = ErrorHandler() h.run(error_app) self.assertEqual(h.stdout.getvalue(), - "Status: 200 OK\r\n" - "\r\n"+MSG) + ("Status: 200 OK\r\n" + "\r\n"+MSG).encode("ascii")) self.failUnless("AssertionError" in h.stderr.getvalue()) @@ -549,7 +552,7 @@ ) shortpat = ( "Status: 200 OK\r\n" "Content-Length: 0\r\n" "\r\n" - ) + ).encode("ascii") for ssw in "FooBar/1.0", None: sw = ssw and "Server: %s\r\n" % ssw or "" @@ -570,11 +573,13 @@ h.server_software = ssw h.run(non_error_app) if proto=="HTTP/0.9": - self.assertEqual(h.stdout.getvalue(),"") + self.assertEqual(h.stdout.getvalue(),b"") else: self.failUnless( - re.match(stdpat%(version,sw), h.stdout.getvalue()), - (stdpat%(version,sw), h.stdout.getvalue()) + re.match((stdpat%(version,sw)).encode("ascii"), + h.stdout.getvalue()), + ((stdpat%(version,sw)).encode("ascii"), + h.stdout.getvalue()) ) # This epilogue is needed for compatibility with the Python 2.5 regrtest module