4

I am trying to make an application that serves a simple HTML form to the user and then calls a function when the user submits the form. It uses wsgiref.simple_server to serve the HTML. The server is encountering an error and I can't understand why. The code is as follows:

#!/usr/bin/python3
from wsgiref.simple_server import make_server
from wsgiref.util import setup_testing_defaults
import webbrowser # open user's web browser to url when server is run
from sys import exc_info
from traceback import format_tb

# Easily serves an html form at path_to_index with style at path_to_style
# Calls on_submit when the form is submitted, passing a dictionary with key
# value pairs { "input name" : submitted_value }
class SimpleServer:
    def __init__(self, port=8000, on_submit=None, index_path="./index.html", css_path="./style.css"):
        self.port = port
        self.on_submit = on_submit
        self.index_path = index_path
        self.css_path = css_path

    # Forwards request to proper method, or returns 404 page
    def wsgi_app(self, environ, start_response):
        urls = [
            (r"^$", self.index),
            (r"404$", self.error_404),
            (r"style.css$", self.css)
        ]

        path = environ.get("PATH_INFO", "").lstrip("/")
        # Call another application if they called a path defined in urls
        for regex, application in urls:
            match = re.search(regex, path)
            # if the match was found, return that page
            if match:
                environ["myapp.url_args"] = match.groups()
                return application(environ, start_response)
        return error_404(environ, start_response)

    # Gives the user a form to submit all their input. If the form has been 
    # submitted, it sends the ouput of self.on_submit(user_input)
    def index(self, environ, start_response):
        # user_input is a dictionary, with keys from the names of the fields
        user_input = parse_qs(environ['QUERY_STRING'])

        # return either the form or the calculations
        index_html = open(self.index_path).read()
        body = index_html if user_input == {} else calculate(user_input)
        mime_type = "text/html" if user_input == {} else "text/plain"

        # return the body of the message
        status = "200 OK"
        headers = [ ("Content-Type", mime_type), 
                    ("Content-Length", str(len(body))) ]
        start_response(status, headers)
        return [body.encode("utf-8")]


    def start_form(self):
        httpd = make_server('', self.port, ExceptionMiddleware(self.wsgi_app))
        url = "http://localhost:" + str(self.port)
        print("Visit " + url)
        # webbrowser.open(url)
        httpd.serve_forever()

if __name__ == "__main__":
    server = SimpleServer()
    server.start_form()

When I run it, I get the error

127.0.0.1 - - [16/Dec/2014 21:15:57] "GET / HTTP/1.1" 500 0
Traceback (most recent call last):
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 138, in run
    self.finish_response()
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 180, in finish_response
    self.write(data)
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 266, in write
    "write() argument must be a bytes instance"
AssertionError: write() argument must be a bytes instance
127.0.0.1 - - [16/Dec/2014 21:15:57] "GET / HTTP/1.1" 500 59
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 49354)
Traceback (most recent call last):
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 138, in run
    self.finish_response()
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 180, in finish_response
    self.write(data)
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 266, in write
    "write() argument must be a bytes instance"
AssertionError: write() argument must be a bytes instance

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 141, in run
    self.handle_error()
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 368, in handle_error
    self.finish_response()
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 180, in finish_response
    self.write(data)
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 274, in write
    self.send_headers()
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 331, in send_headers
    if not self.origin_server or self.client_is_modern():
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 344, in client_is_modern
    return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9'
TypeError: 'NoneType' object is not subscriptable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.4/socketserver.py", line 305, in _handle_request_noblock
    self.process_request(request, client_address)
  File "/usr/lib/python3.4/socketserver.py", line 331, in process_request
    self.finish_request(request, client_address)
  File "/usr/lib/python3.4/socketserver.py", line 344, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python3.4/socketserver.py", line 669, in __init__
    self.handle()
  File "/usr/lib/python3.4/wsgiref/simple_server.py", line 133, in handle
    handler.run(self.server.get_app())
  File "/usr/lib/python3.4/wsgiref/handlers.py", line 144, in run
    self.close()
  File "/usr/lib/python3.4/wsgiref/simple_server.py", line 35, in close
    self.status.split(' ',1)[0], self.bytes_sent
AttributeError: 'NoneType' object has no attribute 'split'

This output doesn't actually include the script I am running, which I am confused about. Any thoughts?

Langston
  • 1,083
  • 10
  • 26

2 Answers2

2

Just to register the solution for this issue, the problem is with len() function.

str(len(body))

It calculate the wrong size and when return the server Content-Length, then it wait more bytes that needed.

Thus, always send bytes using a buffer with UTF-8, follow example:

from io import StringIO
stdout = StringIO()
print("Hello world!", file=stdout)
start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
return [stdout.getvalue().encode("utf-8")]
rafaelnaskar
  • 619
  • 6
  • 11
1

Looking at your code I don't see a direct reason for this error. However, I would strongly advise that unless you're trying to learn how wsgi works (or implement your own framework), you should use an existing micro-framework. WSGI is NOT meant to be used directly by applications. It provides a very thin interface between Python and a web server.

A nice and light framework is bottle.py -- I use it for all Python webapps. But there are many many others, look for "Non Full-Stack Frameworks" in https://wiki.python.org/moin/WebFrameworks.

A nice advantage of bottle is that it's a single file, which makes it easy to distribute with your server.

noamtm
  • 12,435
  • 15
  • 71
  • 107