new http parser. need to add TE support yet

This commit is contained in:
Benoit Chesneau 2010-01-18 00:19:57 +01:00
parent 09bcc05c5c
commit fcbaae054a
16 changed files with 337 additions and 90 deletions

View File

11
examples/djangotest/manage.py Executable file
View File

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

80
examples/djangotest/settings.py Executable file
View File

@ -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'
)

View File

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>gunicorn django example app</title>
<!--[if IE]>
<script>
// allow IE to recognize HTMl5 elements
document.createElement('section');
document.createElement('article');
document.createElement('aside');
document.createElement('footer');
document.createElement('header');
document.createElement('nav');
document.createElement('time');
</script>
<![endif]-->
</head>
<body>
<header id="top">
<h1>test app</h1>
</header>
{% block content %}{% endblock %}
<footer></footer>
</body>
</html>

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<form method="post">
<table>
{{ form.as_table }}
</table>
<input type="submit" id="submit" value="submit">
</form>
<h2>Got</h2>
{% if subject %}
<p><strong>subject:</strong><br>{{ subject}}</p>
<p><strong>message:</strong><br>{{ message }}</p>
{% endif %}
{% endblock content %}

View File

@ -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
"""}

View File

@ -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
})

6
examples/djangotest/urls.py Executable file
View File

@ -0,0 +1,6 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('',
url(r'^$', 'djangotest.testing.views.home'),
)

View File

@ -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
return chunk, data

View File

@ -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',

View File

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

View File

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

View File

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

View File

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