pythonpython-2.7http-redirectwsgiwsgiref

Redirect a user to url with WSGI (no framework)


I am trying to develop a small web application using python's WSGI. For example, if a user chooses Google they would be redirected to google.com, if they chose Facebook they'd be redirected to facebook.com, etc.

from wsgiref.simple_server import make_server
from cgi import parse_qs, escape

main_html = """
<html>
<head><title> Welcome to redirection test page </title> </head>
<body>
    <form method="get" action='/visit'>
        <input type=radio name='site' value=google> Google
        <input type=radio name='site' value=facebook> Facebook
        <input type=submit value=submit>
    </form>
</body>
</html>
"""


def main(environ, start_response):

    response_body = main_html
    print type(response_body)   
    status = '200 OK'

    response_headers = [
                        ('Content-Type','text/html'),
                        ('Content-Length', str(len(response_body)))
                       ]


    start_response(status, response_headers)
    return [response_body]


def visit(environ, start_response):
    qs = parse_qs(environ['QUERY_STRING'])
    dest = qs.ge('site')[0]
    if dest == 'google':
        start_response('301 Moved Permanently', [('Location','http://google.com')])
    else:
        start_response('301 Moved Permanently', [('Location','http://facebook.com')])

    return [1]


def app(environ, start_response):
    if environ['PATH_INFO'] == '/':
        return main(environ, start_response)
    elif environ['PATH_INFO'] == '/visit':
        return visit(environ, start_response)

httpd =  make_server('192.168.48.128',8052, app)
print 'Serving on port 8052'
httpd.serve_forever()

However, when I run this code, I get the following error:

Traceback (most recent call last):
  File "/usr/lib/python2.7/wsgiref/handlers.py", line 86, in run
    self.finish_response()
  File "/usr/lib/python2.7/wsgiref/handlers.py", line 131, in finish_response
    self.close()
  File "/usr/lib/python2.7/wsgiref/simple_server.py", line 33, in close
    self.status.split(' ',1)[0], self.bytes_sent
AttributeError: 'NoneType' object has no attribute 'split'

Solution

  • You have two problems, both within visit(). The first is a typo, and the second is a failure to adhere to the WSGI specification

    def visit(environ, start_response):
        qs = parse_qs(environ['QUERY_STRING'])
        dest = qs.ge('site')[0]
        if dest == 'google':
            start_response('301 Moved Permanently', [('Location','http://google.com')])
        else:
            start_response('301 Moved Permanently', [('Location','http://facebook.com')])
    
        return [1]
    

    Look at the line dest = qs.ge('site')[0]. qs is just a dictionary, and doesn't have a method ge, just get. Furthermore, cgi.parse_qs is deprecated - use urlparse.parse_qs() instead. Fixing that gets us a new error (my address is 127.0.0.1 because I just ran it off of localhost):

    127.0.0.1 - - [05/Feb/2015 16:45:17] "GET /visit?site=facebook HTTP/1.1" 302 0 
    Traceback (most recent call last):
      File "C:\Python27\lib\wsgiref\handlers.py", line 86, in run
        self.finish_response()
      File "C:\Python27\lib\wsgiref\handlers.py", line 128, in finish_response
        self.write(data)
      File "C:\Python27\lib\wsgiref\handlers.py", line 204, in write
        assert type(data) is StringType, "write() argument must be string"
    AssertionError: write() argument must be string
    

    This tells us that something should be a string, but isn't. Looking again at visit(), at the return line, reveals the problem. You're returning [1] instead of ['1']. Fixing that error makes everything work fine. You'll find that this behavior is documented in PEP 333

    The start_response callable must return a write(body_data) callable that takes one positional parameter: a string to be written as part of the HTTP response body.

    Lastly, as per this answer, you should probably be using a '302 Found' status instead of '301 Moved Permanently' for a redirect, but it works either way.

    Replacing the function with this should fix your issue:

    def visit(environ, start_response):
        qs = parse_qs(environ['QUERY_STRING'])
        dest = qs.get('site')[0]
        if dest == 'google':
            start_response('302 Found', [('Location','http://google.com')])
        else:
            start_response('302 Found', [('Location','http://facebook.com')])
    
        return ['1']