diff --git a/examples/djangotest/__init__.py b/examples/djangotest/__init__.py
new file mode 100755
index 00000000..e69de29b
diff --git a/examples/djangotest/manage.py b/examples/djangotest/manage.py
new file mode 100755
index 00000000..5e78ea97
--- /dev/null
+++ b/examples/djangotest/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/examples/djangotest/settings.py b/examples/djangotest/settings.py
new file mode 100755
index 00000000..cedf9937
--- /dev/null
+++ b/examples/djangotest/settings.py
@@ -0,0 +1,80 @@
+# Django settings for djangotest project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = 'test.db' # Or path to database file if using sqlite3.
+DATABASE_USER = '' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '+$ke3e&)ai+p2vzg@!ku9m=xq=b02-jam9m=_w%n*ys@a8r8va'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'djangotest.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'djangotest.testing'
+)
diff --git a/examples/djangotest/testing/__init__.py b/examples/djangotest/testing/__init__.py
new file mode 100755
index 00000000..e69de29b
diff --git a/examples/djangotest/testing/models.py b/examples/djangotest/testing/models.py
new file mode 100755
index 00000000..d49766e4
--- /dev/null
+++ b/examples/djangotest/testing/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
\ No newline at end of file
diff --git a/examples/djangotest/testing/templates/base.html b/examples/djangotest/testing/templates/base.html
new file mode 100644
index 00000000..146b49f7
--- /dev/null
+++ b/examples/djangotest/testing/templates/base.html
@@ -0,0 +1,33 @@
+
+
+
+
+ gunicorn django example app
+
+
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+
+
+
diff --git a/examples/djangotest/testing/templates/home.html b/examples/djangotest/testing/templates/home.html
new file mode 100644
index 00000000..0570a5a1
--- /dev/null
+++ b/examples/djangotest/testing/templates/home.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+
+ Got
+ {% if subject %}
+ subject:
{{ subject}}
+ message:
{{ message }}
+ {% endif %}
+{% endblock content %}
\ No newline at end of file
diff --git a/examples/djangotest/testing/tests.py b/examples/djangotest/testing/tests.py
new file mode 100755
index 00000000..2247054b
--- /dev/null
+++ b/examples/djangotest/testing/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/examples/djangotest/testing/views.py b/examples/djangotest/testing/views.py
new file mode 100755
index 00000000..d30966f7
--- /dev/null
+++ b/examples/djangotest/testing/views.py
@@ -0,0 +1,31 @@
+# Create your views here.
+
+from django import forms
+from django.shortcuts import render_to_response
+
+class MsgForm(forms.Form):
+ subject = forms.CharField(max_length=100)
+ message = forms.CharField()
+
+
+def home(request):
+
+ subject = None
+ message = None
+ if request.POST:
+ form = MsgForm(request.POST)
+ if form.is_valid():
+ subject = form.cleaned_data['subject']
+ message = form.cleaned_data['message']
+ else:
+ form = MsgForm()
+
+
+ return render_to_response('home.html', {
+ 'form': form,
+ 'subject': subject,
+ 'message': message
+ })
+
+
+
\ No newline at end of file
diff --git a/examples/djangotest/urls.py b/examples/djangotest/urls.py
new file mode 100755
index 00000000..ae319bf5
--- /dev/null
+++ b/examples/djangotest/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ url(r'^$', 'djangotest.testing.views.home'),
+)
+
diff --git a/gunicorn/http/http_parser.py b/gunicorn/http/http_parser.py
index fc505ea3..bf83539f 100644
--- a/gunicorn/http/http_parser.py
+++ b/gunicorn/http/http_parser.py
@@ -103,7 +103,7 @@ class HttpParser(object):
return (transfert_encoding == "chunked")
@property
- def content_length(self):
+ def content_len(self):
transfert_encoding = self._headers.get('Transfer-Encoding')
content_length = self._headers.get('Content-Length')
if transfert_encoding is None:
@@ -115,24 +115,33 @@ class HttpParser(object):
def body_eof(self):
#TODO : add chunk
- if self._len_content == 0:
+ if self._content_len == 0:
return True
return False
-
- def fetch_body(self, buf, data):
+
+ def read_chunk(self, data):
dlen = len(data)
- resize(buf, sizeof(data))
- s = data.value
+ i = data.find("\n")
+ if i != -1:
+ chunk = data[:i].strip().split(";", 1)
+ chunk_size = int(line.pop(0), 16)
+ if chunk_size <= 0:
+ self._chunk_eof = True
+ return None
+ self.start_offset = i+1
+
+ def filter_body(self, data):
+ dlen = len(data)
+ chunk = None
if self.is_chunked:
- # do chunk
pass
else:
- if self.content_len > 0:
- nr = min(len(data), self._content_len)
- # addessof may be not needed here
- memmove(addressof(buf), addressof(data), nr)
+ if self._content_len > 0:
+ nr = min(dlen, self._content_len)
+ print nr
+ chunk = data[:nr]
self._content_len -= nr
- data.value = None
- resize(buf, nr)
+ data = None
+
self.start_offset = 0
- return data
\ No newline at end of file
+ return chunk, data
\ No newline at end of file
diff --git a/gunicorn/http/request.py b/gunicorn/http/request.py
index 94b4ce10..977dc1fd 100644
--- a/gunicorn/http/request.py
+++ b/gunicorn/http/request.py
@@ -42,7 +42,7 @@ from ..util import CHUNK_SIZE, read_partial
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
-log = logging.getLogger(__name__)
+
def _normalize_name(name):
return "-".join([w.lower().capitalize() for w in name.split("-")])
@@ -54,9 +54,8 @@ class RequestError(Exception):
class HTTPRequest(object):
SERVER_VERSION = "gunicorn/%s" % __version__
-
- def __init__(self, socket, client_address, server_address, wid):
- self.wid = wid
+
+ def __init__(self, socket, client_address, server_address):
self.socket = socket
self.client_address = client_address
self.server_address = server_address
@@ -66,6 +65,8 @@ class HTTPRequest(object):
self.parser = HttpParser()
self.start_response_called = False
+ self.log = logging.getLogger(__name__)
+
def read(self):
headers = {}
remain = CHUNK_SIZE
@@ -79,14 +80,15 @@ class HTTPRequest(object):
buf += data
i = self.parser.headers(headers, buf)
if i != -1: break
-
+ if not buf:
+ self.socket.close()
if not headers:
- return
+ return {}
buf = buf[i:]
- log.info("worker %s. got headers:\n%s" % (self.wid, headers))
+ self.log.info("Got headers:\n%s" % headers)
if headers.get('Except', '').lower() == "100-continue":
self.socket.send("100 Continue\n")
@@ -97,10 +99,13 @@ class HTTPRequest(object):
path_info = self.parser.path
query = ""
- if not self.parser.content_length and not self.parser.is_chunked:
+ if not self.parser.content_len and not self.parser.is_chunked:
wsgi_input = StringIO.StringIO()
else:
- wsgi_input = TeeInput(self.socket, parser, buf, remain)
+ wsgi_input = TeeInput(self.socket, self.parser, buf)
+
+
+
environ = {
"wsgi.url_scheme": 'http',
diff --git a/gunicorn/http/response.py b/gunicorn/http/response.py
index 0b46a7f7..81b29c2a 100644
--- a/gunicorn/http/response.py
+++ b/gunicorn/http/response.py
@@ -25,9 +25,10 @@
# OTHER DEALINGS IN THE SOFTWARE.
import errno
+import socket
import time
-from ..util import http_date, write
+from ..util import http_date, write, read_partial
class HTTPResponse(object):
@@ -40,26 +41,25 @@ class HTTPResponse(object):
self.SERVER_VERSION = req.SERVER_VERSION
def send(self):
- if self.req.parser.headers:
- # send headers
- resp_head = []
- resp_head.append("HTTP/1.0 %s\r\n" % (self.status))
+ # send headers
+ resp_head = []
+ resp_head.append("HTTP/1.0 %s\r\n" % (self.status))
+
+ resp_head.append("Server: %s\r\n" % self.SERVER_VERSION)
+ resp_head.append("Date: %s\r\n" % http_date())
+ # broken clients
+ resp_head.append("Status: %s\r\n" % str(self.status))
+ # always close the conenction
+ #resp_head.append("Connection: close\r\n")
+ for name, value in self.headers.items():
+ resp_head.append("%s: %s\r\n" % (name, value))
- resp_head.append("Server: %s\r\n" % self.SERVER_VERSION)
- resp_head.append("Date: %s\r\n" % http_date())
- # broken clients
- resp_head.append("Status: %s\r\n" % str(self.status))
- # always close the conenction
- resp_head.append("Connection: close\r\n")
- for name, value in self.headers.items():
- resp_head.append("%s: %s\r\n" % (name, value))
-
- write(self.sock, "%s\r\n" % "".join(resp_head))
+ write(self.sock, "%s\r\n" % "".join(resp_head))
- for chunk in self.data:
- write(self.sock, chunk)
-
+ for chunk in self.data:
+ write(self.sock, chunk)
+
self.sock.close()
if hasattr(self.data, "close"):
- self.data.close()
+ self.data.close()
\ No newline at end of file
diff --git a/gunicorn/http/tee.py b/gunicorn/http/tee.py
index 015d16b1..b344cbbc 100644
--- a/gunicorn/http/tee.py
+++ b/gunicorn/http/tee.py
@@ -40,30 +40,31 @@ from ..util import MAX_BODY, CHUNK_SIZE
class TeeInput(object):
- def __init__(self, socket, parser, buf, remain):
+ def __init__(self, socket, parser, buf):
self.buf = buf
- self.remain = remain
self.parser = parser
self.socket = socket
- self._len = parser.content_length
+ self._len = parser.content_len
if self._len and self._len < MAX_BODY:
self.tmp = StringIO.StringIO()
else:
self.tmp = tempfile.TemporaryFile()
- self.buf2 = create_string_buffer(tmp)
+
if len(buf) > 0:
- parser.filter_body(self.buf2, buf)
+ chunk, self.buf = parser.filter_body(buf)
+ print chunk
+ if chunk:
+ self.tmp.write(chunk)
+ self.tmp.seek(0)
self._finalize()
- self.tmp.write(self.buf2)
- self.tmp.seek(0)
@property
def len(self):
if self._len: return self._len
- if self.remain:
+ if self.socket:
pos = self.tmp.tell()
while True:
- if not self._tee(self.remain, self.buf2):
+ if not self._tee(CHUNK_SIZE):
break
self.tmp.seek(pos)
self._len = self._tmp_size()
@@ -72,56 +73,86 @@ class TeeInput(object):
def read(self, length=None):
""" read """
- if not self.remain:
+ print "la"
+ if not self.socket:
return self.tmp.read(length)
- if not length:
+ if length is None:
+ print "ici"
r = self.tmp.read() or ""
- while self._tee(self.remain, self.buf2):
- r += self.buf2.value
+ while True:
+ chunk = self._tee(CHUNK_SIZE)
+ if not chunk: break
+ r += chunk
return r
else:
- r = self.buf2
diff = self._tmp_size() - self.tmp.tell()
if not diff:
- return self._ensure_length(self._tee(self.remain, r), self.remain)
+ return self._ensure_length(self._tee(length), length)
else:
- length = min(diff, self.remain)
- return self._ensure_length(self._tee(length, r), length)
+ l = min(diff, length)
+ return self._ensure_length(self.tmp.read(l), length)
- def readline(self, amt=-1):
- pass
+ def readline(self, size=-1):
+ if not self.socket:
+ return self.tmp.readline(size)
+ orig_size = self._tmp_size()
+ if self.tmp.tell() == orig_size:
+ if not self._tee(CHUNK_SIZE):
+ return ''
+ self.tmp.seek(orig_size)
+
+ # now we can get line
+ line = self.tmp.readline()
+ if size > 0 and len(line) < size:
+ self.tmp.seek(orig_size)
+ while True:
+ if not self._tee(CHUNK_SIZE):
+ self.tmp.seek(orig_size)
+ return self.temp.readline(size)
+ return line
+
def readlines(self, sizehints=0):
- pass
+ lines = []
+ line = self.readline()
+ while line:
+ lines.append(line)
+ total += len(line)
+ if 0 < sizehint <= total:
+ break
+ line = self.readline()
+ return lines
- def __next__(self):
+
+ def next(self):
r = self.readline()
if not r:
raise StopIteration
return r
- next = __next__
+ __next__ = next
def __iter__(self):
return self
- def _tee(self, length, dst):
+ def _tee(self, length):
""" fetch partial body"""
- while not self.parser.body_eof() and self.remain:
- data = create_string_buffer(length)
- length -= self.socket.recv_into(data, length)
- self.remain = length
- if self.parser.filter_body(dst, data):
- self.tmp.write(dst.value)
+ while not self.parser.body_eof():
+ data = read_partial(self.socket, length)
+ self.buf += data
+ chunk, self.buf = self.parser.filter_body(self.buf)
+ if chunk:
+ self.tmp.write(chunk)
self.tmp.seek(0, os.SEEK_END)
- return dst
+ return chunk
self._finalize()
return ""
def _finalize(self):
""" here we wil fetch final trailers
if any."""
-
+ if self.parser.body_eof():
+ self.socket = None
def _tmp_size(self):
if isinstance(self.tmp, StringIO.StringIO):
@@ -133,6 +164,5 @@ class TeeInput(object):
if not buf or not self._len:
return buf
while len(buf) < length and self.len != self.tmp.pos():
- buf += self._tee(length - len(buf), self.buf2)
-
+ buf += self._tee(length - len(buf))
return buf
\ No newline at end of file
diff --git a/gunicorn/util.py b/gunicorn/util.py
index 2a846565..556824c9 100644
--- a/gunicorn/util.py
+++ b/gunicorn/util.py
@@ -55,14 +55,13 @@ def read_partial(sock, length):
return data
def write(sock, data):
- for i in xrange(2):
- print i
+ for i in range(2):
try:
return sock.send(data)
- except socket.error:
- if i == 2:
- print "raise"
- raise
+ except socket.error, e:
+ if i == 0:
+ continue
+ raise
def write_nonblock(sock, data):
while True:
@@ -71,9 +70,10 @@ def write_nonblock(sock, data):
if ret[1]: break
except socket.error, e:
if e[0] == errno.EINTR:
+ time.sleep(1)
break
raise
- sock.send(data)
+ write(sock, data)
def import_app(module):
parts = module.rsplit(":", 1)
diff --git a/gunicorn/worker.py b/gunicorn/worker.py
index 0fb1a1e9..bb85e7e2 100644
--- a/gunicorn/worker.py
+++ b/gunicorn/worker.py
@@ -38,8 +38,6 @@ import time
from . import http
from . import util
-log = logging.getLogger(__name__)
-
class Worker(object):
SIGNALS = map(
@@ -63,6 +61,8 @@ class Worker(object):
self.app = app
self.alive = True
+
+ self.log = logging.getLogger(__name__)
def close_on_exec(self, fd):
flags = fcntl.fcntl(fd, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
@@ -115,7 +115,10 @@ class Worker(object):
# loop and wait for some lovin.
while self.alive:
try:
- client, addr = self.socket.accept()
+ res = self.socket.accept()
+ if res is None:
+ break
+ client, addr = res
client.setblocking(0)
# handle connection
@@ -130,18 +133,15 @@ class Worker(object):
errno.EWOULDBLOCK]:
break # Uh oh!
raise
-
-
-
def handle(self, client, addr):
self.close_on_exec(client)
try:
- req = http.HTTPRequest(client, addr, self.address, self.id)
+ req = http.HTTPRequest(client, addr, self.address)
response = self.app(req.read(), req.start_response)
http.HTTPResponse(client, response, req).send()
except Exception, e:
- log.exception("Error processing request. [%s]" % str(e))
+ self.log.exception("Error processing request. [%s]" % str(e))
msg = "HTTP/1.0 500 Internal Server Error\r\n\r\n"
util.write_nonblock(client, msg)
client.close()