<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -2,7 +2,6 @@ from itty import *
 
 @error(500)
 def my_great_500(request, exception):
-    request._start_response('500 APPLICATION ERROR', [('Content-Type', 'text/html')])
     html_output = &quot;&quot;&quot;
     &lt;html&gt;
         &lt;head&gt;
@@ -18,7 +17,8 @@ def my_great_500(request, exception):
         &lt;/body&gt;
     &lt;/html&gt;
     &quot;&quot;&quot; % exception
-    return [html_output]
+    response = Response(html_output, status=500)
+    return response.send(request._start_response)
 
 @get('/hello')
 def hello(request):</diff>
      <filename>examples/error_handling.py</filename>
    </modified>
    <modified>
      <diff>@@ -2,19 +2,24 @@ from itty import *
 
 @get('/ct')
 def ct(request):
-    ct.content_type = 'text/plain'
-    return 'Check your Content-Type headers.'
+    response = Response('Check your Content-Type headers.', content_type='text/plain')
+    return response
 
 @get('/headers')
 def test_headers(request):
-    test_headers.headers = [
+    headers = [
         ('X-Powered-By', 'itty'),
         ('Set-Cookie', 'username=daniel')
     ]
-    return 'Check your headers.'
+    response = Response('Check your headers.', headers=headers)
+    return response
+
+@get('/redirected')
+def index(request):
+    return 'You got redirected!'
 
 @get('/test_redirect')
 def test_redirect(request):
-    raise Redirect('/hello')
+    raise Redirect('/redirected')
 
 run_itty()</diff>
      <filename>examples/http_header_support.py</filename>
    </modified>
    <modified>
      <diff>@@ -24,7 +24,6 @@ import cgi
 import mimetypes
 import os
 import re
-import sys
 import urlparse
 try:
     from urlparse import parse_qs
@@ -32,7 +31,7 @@ except ImportError:
     from cgi import parse_qs
 
 __author__ = 'Daniel Lindsley'
-__version__ = ('0', '5', '1')
+__version__ = ('0', '6', '0')
 __license__ = 'BSD'
 
 
@@ -48,47 +47,47 @@ ERROR_HANDLERS = {}
 MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'media')
 
 HTTP_MAPPINGS = {
-    100: '100 CONTINUE',
-    101: '101 SWITCHING PROTOCOLS',
-    200: '200 OK',
-    201: '201 CREATED',
-    202: '202 ACCEPTED',
-    203: '203 NON-AUTHORITATIVE INFORMATION',
-    204: '204 NO CONTENT',
-    205: '205 RESET CONTENT',
-    206: '206 PARTIAL CONTENT',
-    300: '300 MULTIPLE CHOICES',
-    301: '301 MOVED PERMANENTLY',
-    302: '302 FOUND',
-    303: '303 SEE OTHER',
-    304: '304 NOT MODIFIED',
-    305: '305 USE PROXY',
-    306: '306 RESERVED',
-    307: '307 TEMPORARY REDIRECT',
-    400: '400 BAD REQUEST',
-    401: '401 UNAUTHORIZED',
-    402: '402 PAYMENT REQUIRED',
-    403: '403 FORBIDDEN',
-    404: '404 NOT FOUND',
-    405: '405 METHOD NOT ALLOWED',
-    406: '406 NOT ACCEPTABLE',
-    407: '407 PROXY AUTHENTICATION REQUIRED',
-    408: '408 REQUEST TIMEOUT',
-    409: '409 CONFLICT',
-    410: '410 GONE',
-    411: '411 LENGTH REQUIRED',
-    412: '412 PRECONDITION FAILED',
-    413: '413 REQUEST ENTITY TOO LARGE',
-    414: '414 REQUEST-URI TOO LONG',
-    415: '415 UNSUPPORTED MEDIA TYPE',
-    416: '416 REQUESTED RANGE NOT SATISFIABLE',
-    417: '417 EXPECTATION FAILED',
-    500: '500 INTERNAL SERVER ERROR',
-    501: '501 NOT IMPLEMENTED',
-    502: '502 BAD GATEWAY',
-    503: '503 SERVICE UNAVAILABLE',
-    504: '504 GATEWAY TIMEOUT',
-    505: '505 HTTP VERSION NOT SUPPORTED',
+    100: 'CONTINUE',
+    101: 'SWITCHING PROTOCOLS',
+    200: 'OK',
+    201: 'CREATED',
+    202: 'ACCEPTED',
+    203: 'NON-AUTHORITATIVE INFORMATION',
+    204: 'NO CONTENT',
+    205: 'RESET CONTENT',
+    206: 'PARTIAL CONTENT',
+    300: 'MULTIPLE CHOICES',
+    301: 'MOVED PERMANENTLY',
+    302: 'FOUND',
+    303: 'SEE OTHER',
+    304: 'NOT MODIFIED',
+    305: 'USE PROXY',
+    306: 'RESERVED',
+    307: 'TEMPORARY REDIRECT',
+    400: 'BAD REQUEST',
+    401: 'UNAUTHORIZED',
+    402: 'PAYMENT REQUIRED',
+    403: 'FORBIDDEN',
+    404: 'NOT FOUND',
+    405: 'METHOD NOT ALLOWED',
+    406: 'NOT ACCEPTABLE',
+    407: 'PROXY AUTHENTICATION REQUIRED',
+    408: 'REQUEST TIMEOUT',
+    409: 'CONFLICT',
+    410: 'GONE',
+    411: 'LENGTH REQUIRED',
+    412: 'PRECONDITION FAILED',
+    413: 'REQUEST ENTITY TOO LARGE',
+    414: 'REQUEST-URI TOO LONG',
+    415: 'UNSUPPORTED MEDIA TYPE',
+    416: 'REQUESTED RANGE NOT SATISFIABLE',
+    417: 'EXPECTATION FAILED',
+    500: 'INTERNAL SERVER ERROR',
+    501: 'NOT IMPLEMENTED',
+    502: 'BAD GATEWAY',
+    503: 'SERVICE UNAVAILABLE',
+    504: 'GATEWAY TIMEOUT',
+    505: 'HTTP VERSION NOT SUPPORTED',
 }
 
 
@@ -186,22 +185,42 @@ class Request(object):
         return query_dict
 
 
+class Response(object):
+    headers = []
+    
+    def __init__(self, output, headers=None, status=200, content_type='text/html'):
+        self.output = output
+        self.content_type = content_type
+        self.status = status
+        self.headers = []
+        
+        if headers and isinstance(headers, list):
+            self.headers = headers
+    
+    def add_header(self, key, value):
+        self.headers.append((key, value))
+    
+    def send(self, start_response):
+        status = &quot;%d %s&quot; % (self.status, HTTP_MAPPINGS.get(self.status))
+        headers = [('Content-Type', self.content_type)] + self.headers
+        start_response(status, headers)
+        return self.output
+
+
 def handle_request(environ, start_response):
     &quot;&quot;&quot;The main handler. Dispatches to the user's code.&quot;&quot;&quot;
     try:
         request = Request(environ, start_response)
         
         (re_url, url, callback), kwargs = find_matching_url(request)
-        output = callback(request, **kwargs)
+        response = callback(request, **kwargs)
     except Exception, e:
         return handle_error(e, request)
     
-    ct = getattr(callback, 'content_type', 'text/html')
-    status = getattr(callback, 'status', 200)
-    headers = getattr(callback, 'headers', [])
+    if not isinstance(response, Response):
+        response = Response(response)
     
-    start_response(HTTP_MAPPINGS.get(status), [('Content-Type', ct)] + headers)
-    return output
+    return response.send(start_response)
 
 
 def handle_error(exception, request):
@@ -349,26 +368,26 @@ def error(code):
 
 @error(403)
 def forbidden(request, exception):
-    request._start_response(HTTP_MAPPINGS[403], [('Content-Type', 'text/plain')])
-    return ['Forbidden']
+    response = Response('Forbidden', status=403, content_type='text/plain')
+    return response.send(request._start_response)
 
 
 @error(404)
 def not_found(request, exception):
-    request._start_response(HTTP_MAPPINGS[404], [('Content-Type', 'text/plain')])
-    return ['Not Found']
+    response = Response('Not Found', status=404, content_type='text/plain')
+    return response.send(request._start_response)
 
 
 @error(500)
 def app_error(request, exception):
-    request._start_response(HTTP_MAPPINGS[500], [('Content-Type', 'text/plain')])
-    return ['Application Error']
+    response = Response('Application Error', status=500, content_type='text/plain')
+    return response.send(request._start_response)
 
 
 @error(302)
 def redirect(request, exception):
-    request._start_response(HTTP_MAPPINGS[302], [('Content-Type', 'text/plain'), ('Location', exception.url)])
-    return ['']
+    response = Response('', status=302, content_type='text/plain', headers=[('Location', exception.url)])
+    return response.send(request._start_response)
 
 
 # Servers Adapters
@@ -404,8 +423,7 @@ def paste_adapter(host, port):
 
 
 def twisted_adapter(host, port):
-    from twisted.application import service, strports
-    from twisted.web import server, http, wsgi
+    from twisted.web import server, wsgi
     from twisted.python.threadpool import ThreadPool
     from twisted.internet import reactor
     
@@ -457,10 +475,4 @@ def run_itty(server='wsgiref', host='localhost', port=8080, config=None):
         print 'Use Ctrl-C to quit.'
         print
     
-    try:
-        WSGI_ADAPTERS[server](host, port)
-    except KeyboardInterrupt:
-        if server != 'appengine':
-            print &quot;Shuting down...&quot;
-        
-        sys.exit()
+    WSGI_ADAPTERS[server](host, port)</diff>
      <filename>itty.py</filename>
    </modified>
    <modified>
      <diff>@@ -4,14 +4,15 @@ from distutils.core import setup
 from itty import __version__
 
 classifiers = ['License :: OSI Approved :: BSD License']
-setup(name='itty',
-      version='%s.%s.%s' % __version__,
-      description='The itty-bitty Python web framework.',
-      long_description=open('README.rst').read(),
-      author='Daniel Lindsley',
-      author_email='daniel@toastdriven.com',
-      url='http://github.com/toastdriven/itty/',
-      py_modules=['itty'],
-      license='BSD',
-      classifiers=classifiers,
-     )
+setup(
+    name='itty',
+    version='%s.%s.%s' % __version__,
+    description='The itty-bitty Python web framework.',
+    long_description=open('README.rst').read(),
+    author='Daniel Lindsley',
+    author_email='daniel@toastdriven.com',
+    url='http://github.com/toastdriven/itty/',
+    py_modules=['itty'],
+    license='BSD',
+    classifiers=classifiers,
+)</diff>
      <filename>setup.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>9ed37beaa47c3bf48aa405e4a1c06f12908f173e</id>
    </parent>
  </parents>
  <author>
    <name>Daniel Lindsley</name>
    <email>daniel@toastdriven.com</email>
  </author>
  <url>http://github.com/toastdriven/itty/commit/020de8f22803873fec7adaa854c26988dac62bee</url>
  <id>020de8f22803873fec7adaa854c26988dac62bee</id>
  <committed-date>2009-07-08T23:44:14-07:00</committed-date>
  <authored-date>2009-07-08T23:44:14-07:00</authored-date>
  <message>Made itty more thread safe by removing response-related assignments to functions.

Slightly backward-incompatible if you were sending custom headers/status/content type. See 'examples/http_header_support.py' for examples. Thanks to defnull for pointing this out.</message>
  <tree>b3701b515c5a37c0b2c9285f3540a18a82a5a311</tree>
  <committer>
    <name>Daniel Lindsley</name>
    <email>daniel@toastdriven.com</email>
  </committer>
</commit>
