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") return (transfert_encoding == "chunked")
@property @property
def content_length(self): def content_len(self):
transfert_encoding = self._headers.get('Transfer-Encoding') transfert_encoding = self._headers.get('Transfer-Encoding')
content_length = self._headers.get('Content-Length') content_length = self._headers.get('Content-Length')
if transfert_encoding is None: if transfert_encoding is None:
@ -115,24 +115,33 @@ class HttpParser(object):
def body_eof(self): def body_eof(self):
#TODO : add chunk #TODO : add chunk
if self._len_content == 0: if self._content_len == 0:
return True return True
return False return False
def fetch_body(self, buf, data): def read_chunk(self, data):
dlen = len(data) dlen = len(data)
resize(buf, sizeof(data)) i = data.find("\n")
s = data.value 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: if self.is_chunked:
# do chunk
pass pass
else: else:
if self.content_len > 0: if self._content_len > 0:
nr = min(len(data), self._content_len) nr = min(dlen, self._content_len)
# addessof may be not needed here print nr
memmove(addressof(buf), addressof(data), nr) chunk = data[:nr]
self._content_len -= nr self._content_len -= nr
data.value = None data = None
resize(buf, nr)
self.start_offset = 0 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]+') NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
log = logging.getLogger(__name__)
def _normalize_name(name): def _normalize_name(name):
return "-".join([w.lower().capitalize() for w in name.split("-")]) return "-".join([w.lower().capitalize() for w in name.split("-")])
@ -54,9 +54,8 @@ class RequestError(Exception):
class HTTPRequest(object): class HTTPRequest(object):
SERVER_VERSION = "gunicorn/%s" % __version__ SERVER_VERSION = "gunicorn/%s" % __version__
def __init__(self, socket, client_address, server_address, wid): def __init__(self, socket, client_address, server_address):
self.wid = wid
self.socket = socket self.socket = socket
self.client_address = client_address self.client_address = client_address
self.server_address = server_address self.server_address = server_address
@ -66,6 +65,8 @@ class HTTPRequest(object):
self.parser = HttpParser() self.parser = HttpParser()
self.start_response_called = False self.start_response_called = False
self.log = logging.getLogger(__name__)
def read(self): def read(self):
headers = {} headers = {}
remain = CHUNK_SIZE remain = CHUNK_SIZE
@ -79,14 +80,15 @@ class HTTPRequest(object):
buf += data buf += data
i = self.parser.headers(headers, buf) i = self.parser.headers(headers, buf)
if i != -1: break if i != -1: break
if not buf:
self.socket.close()
if not headers: if not headers:
return return {}
buf = buf[i:] 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": if headers.get('Except', '').lower() == "100-continue":
self.socket.send("100 Continue\n") self.socket.send("100 Continue\n")
@ -97,10 +99,13 @@ class HTTPRequest(object):
path_info = self.parser.path path_info = self.parser.path
query = "" 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() wsgi_input = StringIO.StringIO()
else: else:
wsgi_input = TeeInput(self.socket, parser, buf, remain) wsgi_input = TeeInput(self.socket, self.parser, buf)
environ = { environ = {
"wsgi.url_scheme": 'http', "wsgi.url_scheme": 'http',

View File

@ -25,9 +25,10 @@
# OTHER DEALINGS IN THE SOFTWARE. # OTHER DEALINGS IN THE SOFTWARE.
import errno import errno
import socket
import time import time
from ..util import http_date, write from ..util import http_date, write, read_partial
class HTTPResponse(object): class HTTPResponse(object):
@ -40,26 +41,25 @@ class HTTPResponse(object):
self.SERVER_VERSION = req.SERVER_VERSION self.SERVER_VERSION = req.SERVER_VERSION
def send(self): def send(self):
if self.req.parser.headers: # send headers
# send headers resp_head = []
resp_head = [] resp_head.append("HTTP/1.0 %s\r\n" % (self.status))
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) write(self.sock, "%s\r\n" % "".join(resp_head))
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))
for chunk in self.data: for chunk in self.data:
write(self.sock, chunk) write(self.sock, chunk)
self.sock.close() self.sock.close()
if hasattr(self.data, "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): class TeeInput(object):
def __init__(self, socket, parser, buf, remain): def __init__(self, socket, parser, buf):
self.buf = buf self.buf = buf
self.remain = remain
self.parser = parser self.parser = parser
self.socket = socket self.socket = socket
self._len = parser.content_length self._len = parser.content_len
if self._len and self._len < MAX_BODY: if self._len and self._len < MAX_BODY:
self.tmp = StringIO.StringIO() self.tmp = StringIO.StringIO()
else: else:
self.tmp = tempfile.TemporaryFile() self.tmp = tempfile.TemporaryFile()
self.buf2 = create_string_buffer(tmp)
if len(buf) > 0: 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._finalize()
self.tmp.write(self.buf2)
self.tmp.seek(0)
@property @property
def len(self): def len(self):
if self._len: return self._len if self._len: return self._len
if self.remain: if self.socket:
pos = self.tmp.tell() pos = self.tmp.tell()
while True: while True:
if not self._tee(self.remain, self.buf2): if not self._tee(CHUNK_SIZE):
break break
self.tmp.seek(pos) self.tmp.seek(pos)
self._len = self._tmp_size() self._len = self._tmp_size()
@ -72,56 +73,86 @@ class TeeInput(object):
def read(self, length=None): def read(self, length=None):
""" read """ """ read """
if not self.remain: print "la"
if not self.socket:
return self.tmp.read(length) return self.tmp.read(length)
if not length: if length is None:
print "ici"
r = self.tmp.read() or "" r = self.tmp.read() or ""
while self._tee(self.remain, self.buf2): while True:
r += self.buf2.value chunk = self._tee(CHUNK_SIZE)
if not chunk: break
r += chunk
return r return r
else: else:
r = self.buf2
diff = self._tmp_size() - self.tmp.tell() diff = self._tmp_size() - self.tmp.tell()
if not diff: if not diff:
return self._ensure_length(self._tee(self.remain, r), self.remain) return self._ensure_length(self._tee(length), length)
else: else:
length = min(diff, self.remain) l = min(diff, length)
return self._ensure_length(self._tee(length, r), length) return self._ensure_length(self.tmp.read(l), length)
def readline(self, amt=-1): def readline(self, size=-1):
pass 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): 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() r = self.readline()
if not r: if not r:
raise StopIteration raise StopIteration
return r return r
next = __next__ __next__ = next
def __iter__(self): def __iter__(self):
return self return self
def _tee(self, length, dst): def _tee(self, length):
""" fetch partial body""" """ fetch partial body"""
while not self.parser.body_eof() and self.remain: while not self.parser.body_eof():
data = create_string_buffer(length) data = read_partial(self.socket, length)
length -= self.socket.recv_into(data, length) self.buf += data
self.remain = length chunk, self.buf = self.parser.filter_body(self.buf)
if self.parser.filter_body(dst, data): if chunk:
self.tmp.write(dst.value) self.tmp.write(chunk)
self.tmp.seek(0, os.SEEK_END) self.tmp.seek(0, os.SEEK_END)
return dst return chunk
self._finalize() self._finalize()
return "" return ""
def _finalize(self): def _finalize(self):
""" here we wil fetch final trailers """ here we wil fetch final trailers
if any.""" if any."""
if self.parser.body_eof():
self.socket = None
def _tmp_size(self): def _tmp_size(self):
if isinstance(self.tmp, StringIO.StringIO): if isinstance(self.tmp, StringIO.StringIO):
@ -133,6 +164,5 @@ class TeeInput(object):
if not buf or not self._len: if not buf or not self._len:
return buf return buf
while len(buf) < length and self.len != self.tmp.pos(): 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 return buf

View File

@ -55,14 +55,13 @@ def read_partial(sock, length):
return data return data
def write(sock, data): def write(sock, data):
for i in xrange(2): for i in range(2):
print i
try: try:
return sock.send(data) return sock.send(data)
except socket.error: except socket.error, e:
if i == 2: if i == 0:
print "raise" continue
raise raise
def write_nonblock(sock, data): def write_nonblock(sock, data):
while True: while True:
@ -71,9 +70,10 @@ def write_nonblock(sock, data):
if ret[1]: break if ret[1]: break
except socket.error, e: except socket.error, e:
if e[0] == errno.EINTR: if e[0] == errno.EINTR:
time.sleep(1)
break break
raise raise
sock.send(data) write(sock, data)
def import_app(module): def import_app(module):
parts = module.rsplit(":", 1) parts = module.rsplit(":", 1)

View File

@ -38,8 +38,6 @@ import time
from . import http from . import http
from . import util from . import util
log = logging.getLogger(__name__)
class Worker(object): class Worker(object):
SIGNALS = map( SIGNALS = map(
@ -63,6 +61,8 @@ class Worker(object):
self.app = app self.app = app
self.alive = True self.alive = True
self.log = logging.getLogger(__name__)
def close_on_exec(self, fd): def close_on_exec(self, fd):
flags = fcntl.fcntl(fd, fcntl.F_GETFD) | fcntl.FD_CLOEXEC flags = fcntl.fcntl(fd, fcntl.F_GETFD) | fcntl.FD_CLOEXEC
@ -115,7 +115,10 @@ class Worker(object):
# loop and wait for some lovin. # loop and wait for some lovin.
while self.alive: while self.alive:
try: try:
client, addr = self.socket.accept() res = self.socket.accept()
if res is None:
break
client, addr = res
client.setblocking(0) client.setblocking(0)
# handle connection # handle connection
@ -130,18 +133,15 @@ class Worker(object):
errno.EWOULDBLOCK]: errno.EWOULDBLOCK]:
break # Uh oh! break # Uh oh!
raise raise
def handle(self, client, addr): def handle(self, client, addr):
self.close_on_exec(client) self.close_on_exec(client)
try: 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) response = self.app(req.read(), req.start_response)
http.HTTPResponse(client, response, req).send() http.HTTPResponse(client, response, req).send()
except Exception, e: 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" msg = "HTTP/1.0 500 Internal Server Error\r\n\r\n"
util.write_nonblock(client, msg) util.write_nonblock(client, msg)
client.close() client.close()