implement request parser object. it isn't useful until i implement the

response and wsgi app passing
This commit is contained in:
Benoit Chesneau 2009-12-01 14:11:56 +01:00
parent 5fd09fa3df
commit 992b538705
4 changed files with 307 additions and 7 deletions

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
#
# Copyright 2008,2009 Benoit Chesneau <benoitc@e-engura.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "0.1"

24
gunicorn/errors.py Normal file
View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
#
# Copyright 2008,2009 Benoit Chesneau <benoitc@e-engura.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class RequestError(Exception):
def __init__(self, status_code, reason):
self.status_code = status_code
self.reason = reason
Exception.__init__(self, (status_code, reason))

255
gunicorn/httprequest.py Normal file
View File

@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
#
# Copyright 2008,2009 Benoit Chesneau <benoitc@e-engura.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import StringIO
from urllib import unquote
from gunicorn import __version__
from gunicorn.errors import RequestError
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
def _normalize_name(name):
return ["-".join([w.capitalize() for w in name.split("-")])]
class HTTPRequest(object):
SERVER_VERSION = "gunicorn/%s" % __version__
CHUNK_SIZE = 4096
def __init__(self, socket, address):
self.socket = socket
self.address = address
self.version = None
self.method = None
self.path = None
self.headers = {}
self.fp = socket.makefile("rw", self.CHUNK_SIZE)
def read(self):
# get status line
self.first_line(self.fp.readline())
# read headers
self.read_headers()
if "?" in self.path:
path_info, query = self.path.split('?', 1)
else:
path_info = self.path
query = ""
length = self.body_length()
print length
if not length:
wsgi_input = StringIO.StringIO()
elif length == "chunked":
length, wsgi_input = self.decode_chunked()
else:
wsgi_input = FileInput(self)
environ = {
"wsgi.url_scheme": 'http',
"wsgi.version": (1, 0),
"wsgi.multithread": False,
"wsgi.multiprocess": True,
"wsgi.run_once": False,
"SCRIPT_NAME": "",
"SERVER_SOFTWARE": self.SERVER_VERSION,
"REQUEST_METHOD": self.method,
"PATH_INFO": unquote(path_info),
"QUERY_STRING": query,
"RAW_URI": self.path,
"CONTENT_TYPE": self.headers.get('content-type', ''),
"CONTENT_LENGTH": length
}
for key, value in self.headers.items():
key = 'HTTP_' + key.replace('-', '_')
if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
environ[key] = value
return environ
def read_headers(self):
hname = ""
while True:
line = self.fp.readline()
if line == "\r\n":
# end of headers
break
if line == "\t":
# It's a continuation line.
self.headers[hname] += line.strip()
else:
try:
hname =self.parse_header(line)
except ValueError:
# bad headers
pass
def body_length(self):
transfert_encoding = self.headers.get('TRANSFERT-ENCODING')
content_length = self.headers.get('CONTENT-LENGTH')
if transfert_encoding is None:
if content_length is None:
return None
return content_length
elif transfert_encoding == "chunked":
return "chunked"
else:
return None
def decode_chunked(self):
"""Decode the 'chunked' transfer coding."""
length = 0
data = StringIO.StringIO()
while True:
line = self.fp.readline().strip().split(";", 1)
chunk_size = int(line.pop(0), 16)
if chunk_size <= 0:
break
length += chunk_size
data.write(self.fp.read(chunk_size))
crlf = self.fp.read(2)
if crlf != "\r\n":
raise RequestError((400, "Bad chunked transfer coding "
"(expected '\\r\\n', got %r)" % crlf))
return
# Grab any trailer headers
self.read_headers()
data.seek(0)
return data, str(length) or ""
def write(self, data):
self.f.write(data)
def close(self, data):
self.fp.close()
self.conn.close()
def first_line(self, line):
method, path, version = line.split(" ")
self.version = version
self.method = method.upper()
self.path = path
def parse_header(self, line):
name, value = line.split(": ", 1)
name = name.upper()
self.headers[name] = value.strip()
return name
class InputFile(object):
def __init__(self, req):
self.length = req.body_length()
self.fp = req.fp
self.eof = False
def close(self):
self.eof = False
def read(self, amt=None):
if self.fp is None or self.eof:
return ''
if amt is None:
# unbounded read
s = self._safe_read(self.length)
self.close() # we read everything
return s
if amt > self.length:
amt = self.length
s = self.fp.read(amt)
self.length -= len(s)
if not self.length:
self.close()
return s
def readline(self, size=None):
if self.fp is None or self.eof:
return ''
if size is not None:
data = self.fp.readline(size)
else:
# User didn't specify a size ...
# We read the line in chunks to make sure it's not a 100MB line !
# cherrypy trick
res = []
while True:
data = self.fp.readline(256)
res.append(data)
if len(data) < 256 or data[-1:] == "\n":
data = ''.join(res)
break
self.length -= len(data)
if not self.length:
self.close()
return data
def readlines(self, sizehint=0):
# Shamelessly stolen from StringIO
total = 0
lines = []
line = self.readline()
while line:
lines.append(line)
total += len(line)
if 0 < sizehint <= total:
break
line = self.readline()
return lines
def _safe_read(self, amt):
"""Read the number of bytes requested, compensating for partial reads.
"""
s = []
while amt > 0:
chunk = self.fp.read(min(amt, MAXAMOUNT))
if not chunk:
raise IncompleteRead(s)
s.append(chunk)
amt -= len(chunk)
return ''.join(s)
def __iter__(self):
return self
def next(self):
if self.eof:
raise StopIteration
return self.readline()

View File

@ -21,9 +21,11 @@ import os
import select
import signal
import socket
import sys
import tempfile
import time
from gunicorn.httprequest import HTTPRequest
from gunicorn import socketserver
from gunicorn.util import NullHandler
@ -93,7 +95,7 @@ class HTTPServer(object):
os.waitpid(-1, 0)
except KeyboardInterrupt:
kill_workers(signal.SIGQUIT)
self.kill_workers(signal.SIGQUIT)
sys.exit()
def init_worker_process(self, worker):
@ -102,12 +104,12 @@ class HTTPServer(object):
def process_client(self, conn, addr):
""" do nothing just echo message"""
flo = conn.makefile()
flo.flush()
message = flo.readline()
flo.write(message)
conn.close()
req = HTTPRequest(conn, addr)
environ = req.read()
req.write(str(environ))
req.close()
def worker_loop(self, worker):
pid = os.fork()
@ -144,7 +146,8 @@ class HTTPServer(object):
break
except errno.EINTR:
ready = self.LISTENERS
except:
except Exception, e:
print str(e)
pass
except KeyboardInterrupt: