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 + + + + +
+

test 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 %} +
+ + {{ form.as_table }} +
+ +
+ +

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()