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 select
import signal import signal
import socket import socket
import sys
import tempfile import tempfile
import time import time
from gunicorn.httprequest import HTTPRequest
from gunicorn import socketserver from gunicorn import socketserver
from gunicorn.util import NullHandler from gunicorn.util import NullHandler
@ -93,7 +95,7 @@ class HTTPServer(object):
os.waitpid(-1, 0) os.waitpid(-1, 0)
except KeyboardInterrupt: except KeyboardInterrupt:
kill_workers(signal.SIGQUIT) self.kill_workers(signal.SIGQUIT)
sys.exit() sys.exit()
def init_worker_process(self, worker): def init_worker_process(self, worker):
@ -102,12 +104,12 @@ class HTTPServer(object):
def process_client(self, conn, addr): def process_client(self, conn, addr):
""" do nothing just echo message""" """ 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): def worker_loop(self, worker):
pid = os.fork() pid = os.fork()
@ -144,7 +146,8 @@ class HTTPServer(object):
break break
except errno.EINTR: except errno.EINTR:
ready = self.LISTENERS ready = self.LISTENERS
except: except Exception, e:
print str(e)
pass pass
except KeyboardInterrupt: except KeyboardInterrupt: