mirror of
https://github.com/frappe/gunicorn.git
synced 2026-01-14 11:09:11 +08:00
new parser using StringIO, faster than concatenate strings. Lot of fixes
in TeeInput.
This commit is contained in:
parent
54d1a8a5dc
commit
c785be0780
@ -3,18 +3,24 @@
|
|||||||
# This file is part of gunicorn released under the MIT license.
|
# This file is part of gunicorn released under the MIT license.
|
||||||
# See the NOTICE for more information.
|
# See the NOTICE for more information.
|
||||||
|
|
||||||
|
from StringIO import StringIO
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
from gunicorn.util import normalize_name
|
class BadStatusLine(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class ParserError(Exception):
|
class ParserError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Parser(object):
|
class Parser(object):
|
||||||
|
""" HTTP Parser compatible 1.0 & 1.1
|
||||||
_should_close = False
|
This parser can parse HTTP requests and response.
|
||||||
|
"""
|
||||||
def __init__(self):
|
|
||||||
|
def __init__(self, ptype='request', should_close=False):
|
||||||
|
self.status_line = ""
|
||||||
|
self.status_int = None
|
||||||
|
self.reason = ""
|
||||||
self.status = ""
|
self.status = ""
|
||||||
self.headers = []
|
self.headers = []
|
||||||
self.headers_dict = {}
|
self.headers_dict = {}
|
||||||
@ -28,7 +34,19 @@ class Parser(object):
|
|||||||
self._content_len = None
|
self._content_len = None
|
||||||
self.start_offset = 0
|
self.start_offset = 0
|
||||||
self.chunk_size = 0
|
self.chunk_size = 0
|
||||||
self._chunk_eof = False
|
self._chunk_eof = False
|
||||||
|
self.type = ptype
|
||||||
|
self._should_close = should_close
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_response(cls, should_close=False):
|
||||||
|
""" Return parser object for response"""
|
||||||
|
return cls(ptype='response', should_close=should_close)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_request(cls):
|
||||||
|
""" return parser object for requests """
|
||||||
|
return cls(ptype='request')
|
||||||
|
|
||||||
def filter_headers(self, headers, buf):
|
def filter_headers(self, headers, buf):
|
||||||
""" take a string as buffer and an header dict
|
""" take a string as buffer and an header dict
|
||||||
@ -36,14 +54,17 @@ class Parser(object):
|
|||||||
if parsing isn't done. headers dict is updated
|
if parsing isn't done. headers dict is updated
|
||||||
with new headers.
|
with new headers.
|
||||||
"""
|
"""
|
||||||
i = buf.find("\r\n\r\n")
|
line = buf.getvalue()
|
||||||
|
i = line.find("\r\n\r\n")
|
||||||
if i != -1:
|
if i != -1:
|
||||||
r = buf[:i]
|
r = line[:i]
|
||||||
pos = i+4
|
pos = i+4
|
||||||
return self.finalize_headers(headers, r, pos)
|
buf2 = StringIO()
|
||||||
return -1
|
buf2.write(line[pos:])
|
||||||
|
return self.finalize_headers(headers, r, buf2)
|
||||||
|
return False
|
||||||
|
|
||||||
def finalize_headers(self, headers, headers_str, pos):
|
def finalize_headers(self, headers, headers_str, buf2):
|
||||||
""" parse the headers """
|
""" parse the headers """
|
||||||
lines = headers_str.split("\r\n")
|
lines = headers_str.split("\r\n")
|
||||||
|
|
||||||
@ -56,7 +77,7 @@ class Parser(object):
|
|||||||
_headers = {}
|
_headers = {}
|
||||||
hname = ""
|
hname = ""
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.startswith("\t") or line.startswith(" "):
|
if line.startswith('\t') or line.startswith(' '):
|
||||||
headers[hname] += line.strip()
|
headers[hname] += line.strip()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@ -68,31 +89,45 @@ class Parser(object):
|
|||||||
headers.extend(list(_headers.items()))
|
headers.extend(list(_headers.items()))
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
self._content_len = int(_headers.get('Content-Length',0))
|
self._content_len = int(_headers.get('Content-Length',0))
|
||||||
(_, _, self.path, self.query_string, self.fragment) = \
|
|
||||||
|
if self.type == 'request':
|
||||||
|
(_, _, self.path, self.query_string, self.fragment) = \
|
||||||
urlparse.urlsplit(self.raw_path)
|
urlparse.urlsplit(self.raw_path)
|
||||||
return pos
|
|
||||||
|
return buf2
|
||||||
|
|
||||||
|
def _parse_version(self, version):
|
||||||
|
self.raw_version = version.strip()
|
||||||
|
try:
|
||||||
|
major, minor = self.raw_version.split("HTTP/")[1].split(".")
|
||||||
|
self.version = (int(major), int(minor))
|
||||||
|
except IndexError:
|
||||||
|
self.version = (1, 0)
|
||||||
|
|
||||||
def _first_line(self, line):
|
def _first_line(self, line):
|
||||||
""" parse first line """
|
""" parse first line """
|
||||||
self.status = status = line.strip()
|
self.status_line = status_line = line.strip()
|
||||||
|
|
||||||
method, path, version = status.split(" ")
|
|
||||||
version = version.strip()
|
|
||||||
self.raw_version = version
|
|
||||||
try:
|
try:
|
||||||
major, minor = version.split("HTTP/")[1].split(".")
|
if self.type == 'response':
|
||||||
version = (int(major), int(minor))
|
version, self.status = status_line.split(None, 1)
|
||||||
except IndexError:
|
self._parse_version(version)
|
||||||
version = (1, 0)
|
try:
|
||||||
|
self.status_int, self.reason = self.status.split(None, 1)
|
||||||
self.version = version
|
except ValueError:
|
||||||
self.method = method.upper()
|
self.status_int = self.status
|
||||||
self.raw_path = path
|
self.status_int = int(self.status_int)
|
||||||
|
else:
|
||||||
|
method, path, version = status_line.split(None, 2)
|
||||||
|
self._parse_version(version)
|
||||||
|
self.method = method.upper()
|
||||||
|
self.raw_path = path
|
||||||
|
except ValueError:
|
||||||
|
raise BadStatusLine(line)
|
||||||
|
|
||||||
def _parse_headerl(self, hdrs, line):
|
def _parse_headerl(self, hdrs, line):
|
||||||
""" parse header line"""
|
""" parse header line"""
|
||||||
name, value = line.split(":", 1)
|
name, value = line.split(":", 1)
|
||||||
name = normalize_name(name.strip())
|
name = name.strip().title()
|
||||||
value = value.rsplit("\r\n",1)[0].strip()
|
value = value.rsplit("\r\n",1)[0].strip()
|
||||||
if name in hdrs:
|
if name in hdrs:
|
||||||
hdrs[name] = "%s, %s" % (hdrs[name], value)
|
hdrs[name] = "%s, %s" % (hdrs[name], value)
|
||||||
@ -108,7 +143,7 @@ class Parser(object):
|
|||||||
return True
|
return True
|
||||||
elif self.headers_dict.get("Connection") == "Keep-Alive":
|
elif self.headers_dict.get("Connection") == "Keep-Alive":
|
||||||
return False
|
return False
|
||||||
elif self.version < (1,0):
|
elif self.version <= (1, 0):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -139,11 +174,14 @@ class Parser(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def read_chunk(self, data):
|
def read_chunk(self, buf):
|
||||||
|
line = buf.getvalue()
|
||||||
|
buf2 = StringIO()
|
||||||
|
|
||||||
if not self.start_offset:
|
if not self.start_offset:
|
||||||
i = data.find("\r\n")
|
i = line.find("\r\n")
|
||||||
if i != -1:
|
if i != -1:
|
||||||
chunk = data[:i].strip().split(";", 1)
|
chunk = line[:i].strip().split(";", 1)
|
||||||
chunk_size = int(chunk.pop(0), 16)
|
chunk_size = int(chunk.pop(0), 16)
|
||||||
self.start_offset = i+2
|
self.start_offset = i+2
|
||||||
self.chunk_size = chunk_size
|
self.chunk_size = chunk_size
|
||||||
@ -151,39 +189,47 @@ class Parser(object):
|
|||||||
if self.start_offset:
|
if self.start_offset:
|
||||||
if self.chunk_size == 0:
|
if self.chunk_size == 0:
|
||||||
self._chunk_eof = True
|
self._chunk_eof = True
|
||||||
ret = '', data[:self.start_offset]
|
buf2.write(line[:self.start_offset])
|
||||||
return ret
|
return '', buf2
|
||||||
else:
|
else:
|
||||||
chunk = data[self.start_offset:self.start_offset+self.chunk_size]
|
chunk = line[self.start_offset:self.start_offset+self.chunk_size]
|
||||||
end_offset = self.start_offset + self.chunk_size + 2
|
end_offset = self.start_offset + self.chunk_size + 2
|
||||||
# we wait CRLF else return None
|
# we wait CRLF else return None
|
||||||
if len(data) >= end_offset:
|
if buf.len >= end_offset:
|
||||||
ret = chunk, data[end_offset:]
|
buf2.write(line[end_offset:])
|
||||||
self.chunk_size = 0
|
self.chunk_size = 0
|
||||||
return ret
|
return chunk, buf2
|
||||||
return '', data
|
return '', buf
|
||||||
|
|
||||||
def trailing_header(self, data):
|
def trailing_header(self, buf):
|
||||||
i = data.find("\r\n\r\n")
|
line = buf.getvalue()
|
||||||
|
i = line.find("\r\n\r\n")
|
||||||
return (i != -1)
|
return (i != -1)
|
||||||
|
|
||||||
def filter_body(self, data):
|
def filter_body(self, buf):
|
||||||
"""\
|
"""\
|
||||||
Filter body and return a tuple: (body_chunk, new_buffer)
|
Filter body and return a tuple: (body_chunk, new_buffer)
|
||||||
Both can be None, and new_buffer is always None if its empty.
|
Both can be None, and new_buffer is always None if its empty.
|
||||||
"""
|
"""
|
||||||
dlen = len(data)
|
dlen = buf.len
|
||||||
chunk = ''
|
chunk = ''
|
||||||
|
|
||||||
if self.is_chunked:
|
if self.is_chunked:
|
||||||
chunk, data = self.read_chunk(data)
|
try:
|
||||||
|
chunk, buf2 = self.read_chunk(buf)
|
||||||
|
except Exception, e:
|
||||||
|
raise ParserError("chunked decoding error [%s]" % str(e))
|
||||||
|
|
||||||
if not chunk:
|
if not chunk:
|
||||||
return '', data
|
return '', buf
|
||||||
else:
|
else:
|
||||||
|
buf2 = StringIO()
|
||||||
if self._content_len > 0:
|
if self._content_len > 0:
|
||||||
nr = min(dlen, self._content_len)
|
nr = min(dlen, self._content_len)
|
||||||
chunk = data[:nr]
|
chunk = buf.getvalue()[:nr]
|
||||||
self._content_len -= nr
|
self._content_len -= nr
|
||||||
data = []
|
|
||||||
|
|
||||||
self.start_offset = 0
|
self.start_offset = 0
|
||||||
return (chunk, data)
|
buf2.seek(0, 2)
|
||||||
|
return (chunk, buf2)
|
||||||
|
|
||||||
|
|||||||
@ -6,14 +6,14 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import StringIO
|
from StringIO import StringIO
|
||||||
import sys
|
import sys
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
|
|
||||||
from gunicorn import __version__
|
from gunicorn import __version__
|
||||||
from gunicorn.http.parser import Parser
|
from gunicorn.http.parser import Parser
|
||||||
from gunicorn.http.tee import TeeInput
|
from gunicorn.http.tee import TeeInput
|
||||||
from gunicorn.util import CHUNK_SIZE, read_partial
|
from gunicorn.util import CHUNK_SIZE
|
||||||
|
|
||||||
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
|
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class Request(object):
|
|||||||
|
|
||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
"wsgi.url_scheme": 'http',
|
"wsgi.url_scheme": 'http',
|
||||||
"wsgi.input": StringIO.StringIO(),
|
"wsgi.input": StringIO(),
|
||||||
"wsgi.errors": sys.stderr,
|
"wsgi.errors": sys.stderr,
|
||||||
"wsgi.version": (1, 0),
|
"wsgi.version": (1, 0),
|
||||||
"wsgi.multithread": False,
|
"wsgi.multithread": False,
|
||||||
@ -39,14 +39,14 @@ class Request(object):
|
|||||||
def __init__(self, socket, client_address, server_address, conf):
|
def __init__(self, socket, client_address, server_address, conf):
|
||||||
self.debug = conf['debug']
|
self.debug = conf['debug']
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.socket = socket
|
self._sock = socket
|
||||||
|
|
||||||
self.client_address = client_address
|
self.client_address = client_address
|
||||||
self.server_address = server_address
|
self.server_address = server_address
|
||||||
self.response_status = None
|
self.response_status = None
|
||||||
self.response_headers = []
|
self.response_headers = []
|
||||||
self._version = 11
|
self._version = 11
|
||||||
self.parser = Parser()
|
self.parser = Parser.parse_request()
|
||||||
self.start_response_called = False
|
self.start_response_called = False
|
||||||
self.log = logging.getLogger(__name__)
|
self.log = logging.getLogger(__name__)
|
||||||
self.response_chunked = False
|
self.response_chunked = False
|
||||||
@ -54,27 +54,30 @@ class Request(object):
|
|||||||
def read(self):
|
def read(self):
|
||||||
environ = {}
|
environ = {}
|
||||||
headers = []
|
headers = []
|
||||||
buf = read_partial(self.socket, CHUNK_SIZE)
|
buf = StringIO()
|
||||||
i = self.parser.filter_headers(headers, buf)
|
data = self._sock.recv(CHUNK_SIZE)
|
||||||
if i == -1 and buf:
|
buf.write(data)
|
||||||
|
buf2 = self.parser.filter_headers(headers, buf)
|
||||||
|
if not buf2:
|
||||||
while True:
|
while True:
|
||||||
data = read_partial(self.socket, CHUNK_SIZE)
|
data = self._sock.recv(CHUNK_SIZE)
|
||||||
if not data: break
|
if not data:
|
||||||
buf += data
|
break
|
||||||
i = self.parser.filter_headers(headers, buf)
|
buf.write(data)
|
||||||
if i != -1:
|
buf2 = self.parser.filter_headers(headers, buf)
|
||||||
break
|
if buf2:
|
||||||
|
break
|
||||||
|
|
||||||
self.log.debug("%s", self.parser.status)
|
self.log.debug("%s", self.parser.status)
|
||||||
self.log.debug("Headers:\n%s" % headers)
|
self.log.debug("Headers:\n%s" % headers)
|
||||||
|
|
||||||
if self.parser.headers_dict.get('Expect','').lower() == "100-continue":
|
if self.parser.headers_dict.get('Expect','').lower() == "100-continue":
|
||||||
self.socket.send("HTTP/1.1 100 Continue\r\n\r\n")
|
self._sock.send("HTTP/1.1 100 Continue\r\n\r\n")
|
||||||
|
|
||||||
if not self.parser.content_len 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()
|
||||||
else:
|
else:
|
||||||
wsgi_input = TeeInput(self.socket, self.parser, buf[i:], self.conf)
|
wsgi_input = TeeInput(self._sock, self.parser, buf2, self.conf)
|
||||||
|
|
||||||
# This value should evaluate true if an equivalent application
|
# This value should evaluate true if an equivalent application
|
||||||
# object may be simultaneously invoked by another process, and
|
# object may be simultaneously invoked by another process, and
|
||||||
|
|||||||
@ -9,7 +9,7 @@ class Response(object):
|
|||||||
|
|
||||||
def __init__(self, sock, response, req):
|
def __init__(self, sock, response, req):
|
||||||
self.req = req
|
self.req = req
|
||||||
self.sock = sock
|
self._sock = sock
|
||||||
self.data = response
|
self.data = response
|
||||||
self.headers = req.response_headers or []
|
self.headers = req.response_headers or []
|
||||||
self.status = req.response_status
|
self.status = req.response_status
|
||||||
@ -18,21 +18,18 @@ class Response(object):
|
|||||||
|
|
||||||
def send(self):
|
def send(self):
|
||||||
# send headers
|
# send headers
|
||||||
resp_head = []
|
resp_head = [
|
||||||
resp_head.append("HTTP/1.1 %s\r\n" % (self.status))
|
"HTTP/1.1 %s\r\n" % self.status,
|
||||||
|
"Server: %s\r\n" % self.SERVER_VERSION,
|
||||||
resp_head.append("Server: %s\r\n" % self.SERVER_VERSION)
|
"Date: %s\r\n" % http_date(),
|
||||||
resp_head.append("Date: %s\r\n" % http_date())
|
"Connection: close\r\n"
|
||||||
# always close the connection
|
]
|
||||||
resp_head.append("Connection: close\r\n")
|
resp_head.extend(["%s: %s\r\n" % (n, v) for n, v in self.headers])
|
||||||
for name, value in self.headers:
|
write(self._sock, "%s\r\n" % "".join(resp_head))
|
||||||
resp_head.append("%s: %s\r\n" % (name, value))
|
|
||||||
|
|
||||||
write(self.sock, "%s\r\n" % "".join(resp_head))
|
|
||||||
|
|
||||||
last_chunk = None
|
last_chunk = None
|
||||||
for chunk in list(self.data):
|
for chunk in list(self.data):
|
||||||
write(self.sock, chunk, self.chunked)
|
write(self._sock, chunk, self.chunked)
|
||||||
last_chunk = chunk
|
last_chunk = chunk
|
||||||
|
|
||||||
if self.chunked:
|
if self.chunked:
|
||||||
@ -40,7 +37,7 @@ class Response(object):
|
|||||||
# send last chunk
|
# send last chunk
|
||||||
write_chunk("")
|
write_chunk("")
|
||||||
|
|
||||||
close(self.sock)
|
close(self._sock)
|
||||||
|
|
||||||
if hasattr(self.data, "close"):
|
if hasattr(self.data, "close"):
|
||||||
self.data.close()
|
self.data.close()
|
||||||
|
|||||||
@ -1,63 +1,68 @@
|
|||||||
# -*- coding: utf-8 -
|
# -*- coding: utf-8 -
|
||||||
#
|
#
|
||||||
# This file is part of gunicorn released under the MIT license.
|
# This file is part of restkit released under the MIT license.
|
||||||
# See the NOTICE for more information.
|
# See the NOTICE for more information.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
TeeInput replace old FileInput. It use a file
|
TeeInput replace old FileInput. It use a file
|
||||||
if size > MAX_BODY or memory. It's now possible to rewind
|
if size > MAX_BODY or memory. It's now possible to rewind
|
||||||
read or restart etc ... It's based on TeeInput from unicorn.
|
read or restart etc ... It's based on TeeInput from Gunicorn.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import StringIO
|
from StringIO import StringIO
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from gunicorn.util import MAX_BODY, CHUNK_SIZE, read_partial
|
from gunicorn import util
|
||||||
|
|
||||||
class TeeInput(object):
|
class TeeInput(object):
|
||||||
|
|
||||||
|
CHUNK_SIZE = util.CHUNK_SIZE
|
||||||
|
|
||||||
def __init__(self, socket, parser, buf, conf):
|
def __init__(self, socket, parser, buf, conf):
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.buf = buf
|
self.buf = StringIO()
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
self.socket = socket
|
self._sock = socket
|
||||||
self._is_socket = True
|
self._is_socket = True
|
||||||
self._len = parser.content_len
|
self._len = parser.content_len
|
||||||
if self._len and self._len < MAX_BODY:
|
|
||||||
self.tmp = StringIO.StringIO()
|
if self._len and self._len < util.MAX_BODY:
|
||||||
|
self.tmp = StringIO()
|
||||||
else:
|
else:
|
||||||
self.tmp = tempfile.TemporaryFile(
|
self.tmp = tempfile.TemporaryFile(dir=self.conf['tmp_upload_dir'])
|
||||||
dir=self.conf['tmp_upload_dir'])
|
|
||||||
|
if buf.len > 0:
|
||||||
if len(buf) > 0:
|
|
||||||
chunk, self.buf = parser.filter_body(buf)
|
chunk, self.buf = parser.filter_body(buf)
|
||||||
if chunk:
|
if chunk:
|
||||||
self.tmp.write(chunk)
|
self.tmp.write(chunk)
|
||||||
self.tmp.flush()
|
self.tmp.flush()
|
||||||
self._finalize()
|
self._finalize()
|
||||||
self.tmp.seek(0)
|
self.tmp.seek(0)
|
||||||
|
del buf
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def len(self):
|
def len(self):
|
||||||
if self._len: return self._len
|
if self._len: return self._len
|
||||||
|
|
||||||
if self._is_socket:
|
if self._is_socket:
|
||||||
pos = self.tmp.tell()
|
pos = self.tmp.tell()
|
||||||
|
self.tmp.seek(0, 2)
|
||||||
while True:
|
while True:
|
||||||
self.tmp.seek(self._tmp_size())
|
if not self._tee(self.CHUNK_SIZE):
|
||||||
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()
|
||||||
return self._len
|
return self._len
|
||||||
|
|
||||||
def seek(self, offset, whence=0):
|
def seek(self, offset, whence=0):
|
||||||
|
""" naive implementation of seek """
|
||||||
if self._is_socket:
|
if self._is_socket:
|
||||||
|
self.tmp.seek(0, 2)
|
||||||
while True:
|
while True:
|
||||||
if not self._tee(CHUNK_SIZE):
|
if not self._tee(self.CHUNK_SIZE):
|
||||||
break
|
break
|
||||||
self.tmp.seek(offset, whence)
|
self.tmp.seek(offset, whence)
|
||||||
|
|
||||||
@ -68,47 +73,54 @@ class TeeInput(object):
|
|||||||
""" read """
|
""" read """
|
||||||
if not self._is_socket:
|
if not self._is_socket:
|
||||||
return self.tmp.read(length)
|
return self.tmp.read(length)
|
||||||
|
|
||||||
if length < 0:
|
if length < 0:
|
||||||
r = self.tmp.read() or ""
|
buf = StringIO()
|
||||||
|
buf.write(self.tmp.read())
|
||||||
while True:
|
while True:
|
||||||
chunk = self._tee(CHUNK_SIZE)
|
chunk = self._tee(self.CHUNK_SIZE)
|
||||||
if not chunk: break
|
if not chunk:
|
||||||
r += chunk
|
break
|
||||||
return r
|
buf.write(chunk)
|
||||||
|
return buf.getvalue()
|
||||||
else:
|
else:
|
||||||
|
dest = StringIO()
|
||||||
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(length), length)
|
dest.write(self._tee(length))
|
||||||
|
return self._ensure_length(dest, length)
|
||||||
else:
|
else:
|
||||||
l = min(diff, length)
|
l = min(diff, length)
|
||||||
return self._ensure_length(self.tmp.read(l), length)
|
dest.write(self.tmp.read(l))
|
||||||
|
return self._ensure_length(dest, length)
|
||||||
|
|
||||||
def readline(self, size=-1):
|
def readline(self, size=-1):
|
||||||
if not self._is_socket:
|
if not self._is_socket:
|
||||||
return self.tmp.readline(size)
|
return self.tmp.readline()
|
||||||
|
|
||||||
orig_size = self._tmp_size()
|
orig_size = self._tmp_size()
|
||||||
if self.tmp.tell() == orig_size:
|
if self.tmp.tell() == orig_size:
|
||||||
if not self._tee(CHUNK_SIZE):
|
if not self._tee(self.CHUNK_SIZE):
|
||||||
return ''
|
return ''
|
||||||
self.tmp.seek(orig_size)
|
self.tmp.seek(orig_size)
|
||||||
|
|
||||||
# now we can get line
|
# now we can get line
|
||||||
line = self.tmp.readline()
|
line = self.tmp.readline()
|
||||||
i = line.find("\n")
|
if line.find("\n") >=0:
|
||||||
if i == -1:
|
return line
|
||||||
while True:
|
|
||||||
orig_size = self.tmp.tell()
|
buf = StringIO()
|
||||||
if not self._tee(CHUNK_SIZE):
|
buf.write(line)
|
||||||
break
|
while True:
|
||||||
self.tmp.seek(orig_size)
|
orig_size = self.tmp.tell()
|
||||||
line += self.tmp.readline()
|
data = self._tee(self.CHUNK_SIZE)
|
||||||
i = line.find("\n")
|
if not data:
|
||||||
if i != -1:
|
break
|
||||||
break
|
self.tmp.seek(orig_size)
|
||||||
|
buf.write(self.tmp.readline())
|
||||||
return line
|
if data.find("\n") >= 0:
|
||||||
|
break
|
||||||
|
return buf.getvalue()
|
||||||
|
|
||||||
def readlines(self, sizehint=0):
|
def readlines(self, sizehint=0):
|
||||||
total = 0
|
total = 0
|
||||||
@ -134,41 +146,49 @@ class TeeInput(object):
|
|||||||
|
|
||||||
def _tee(self, length):
|
def _tee(self, length):
|
||||||
""" fetch partial body"""
|
""" fetch partial body"""
|
||||||
|
buf2 = self.buf
|
||||||
|
buf2.seek(0, 2)
|
||||||
while True:
|
while True:
|
||||||
chunk, self.buf = self.parser.filter_body(self.buf)
|
chunk, buf2 = self.parser.filter_body(buf2)
|
||||||
if chunk:
|
if chunk:
|
||||||
self.tmp.write(chunk)
|
self.tmp.write(chunk)
|
||||||
self.tmp.flush()
|
self.tmp.flush()
|
||||||
self.tmp.seek(0, os.SEEK_END)
|
self.tmp.seek(0, 2)
|
||||||
|
self.buf = StringIO()
|
||||||
|
self.buf.write(buf2.getvalue())
|
||||||
return chunk
|
return chunk
|
||||||
|
|
||||||
if self.parser.body_eof():
|
if self.parser.body_eof():
|
||||||
break
|
break
|
||||||
|
|
||||||
self.buf = read_partial(self.socket, length, self.buf)
|
data = self._sock.recv(length)
|
||||||
|
buf2.write(data)
|
||||||
|
|
||||||
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():
|
if self.parser.body_eof():
|
||||||
del self.buf
|
del self.buf
|
||||||
self._is_socket = False
|
self._is_socket = False
|
||||||
|
|
||||||
def _tmp_size(self):
|
def _tmp_size(self):
|
||||||
if isinstance(self.tmp, StringIO.StringIO):
|
if isinstance(self.tmp, StringIO):
|
||||||
return self.tmp.len
|
return self.tmp.len
|
||||||
else:
|
else:
|
||||||
return int(os.fstat(self.tmp.fileno())[6])
|
return int(os.fstat(self.tmp.fileno())[6])
|
||||||
|
|
||||||
def _ensure_length(self, buf, length):
|
def _ensure_length(self, dest, length):
|
||||||
if not buf or not self._len:
|
if not dest.len or not self._len:
|
||||||
return buf
|
return dest.getvalue()
|
||||||
while True:
|
while True:
|
||||||
if len(buf) >= length:
|
if dest.len >= length:
|
||||||
break
|
break
|
||||||
data = self._tee(length - len(buf))
|
data = self._tee(length - dest.len)
|
||||||
if not data: break
|
if not data:
|
||||||
buf += data
|
break
|
||||||
return buf
|
dest.write(data)
|
||||||
|
return dest.getvalue()
|
||||||
|
|||||||
@ -88,13 +88,6 @@ def close(sock):
|
|||||||
sock.close()
|
sock.close()
|
||||||
except socket.error:
|
except socket.error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def read_partial(sock, length, buf=None):
|
|
||||||
tmp_buf = sock.recv(length)
|
|
||||||
if not buf:
|
|
||||||
return tmp_buf
|
|
||||||
|
|
||||||
return buf + tmp_buf
|
|
||||||
|
|
||||||
def write_chunk(sock, data):
|
def write_chunk(sock, data):
|
||||||
chunk = "".join(("%X\r\n" % len(data), data, "\r\n"))
|
chunk = "".join(("%X\r\n" % len(data), data, "\r\n"))
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import t
|
|||||||
@t.request("001.http")
|
@t.request("001.http")
|
||||||
def test_001(buf, p):
|
def test_001(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
|
|
||||||
t.eq(p.method, "PUT")
|
t.eq(p.method, "PUT")
|
||||||
t.eq(p.version, (1,0))
|
t.eq(p.version, (1,0))
|
||||||
@ -20,15 +20,15 @@ def test_001(buf, p):
|
|||||||
('Content-Type', 'application/json'),
|
('Content-Type', 'application/json'),
|
||||||
('Server', 'http://127.0.0.1:5984')
|
('Server', 'http://127.0.0.1:5984')
|
||||||
])
|
])
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, '{"nom": "nom"}')
|
t.eq(body, '{"nom": "nom"}')
|
||||||
t.eq(p.body_eof(), True)
|
t.eq(p.body_eof(), True)
|
||||||
|
|
||||||
@t.request("002.http")
|
@t.request("002.http")
|
||||||
def test_002(buf, p):
|
def test_002(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
|
|
||||||
t.eq(p.method, "GET")
|
t.eq(p.method, "GET")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
@ -39,14 +39,14 @@ def test_002(buf, p):
|
|||||||
("Host", "0.0.0.0=5000"),
|
("Host", "0.0.0.0=5000"),
|
||||||
("User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1")
|
("User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1")
|
||||||
])
|
])
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, "")
|
t.eq(body, "")
|
||||||
|
|
||||||
@t.request("003.http")
|
@t.request("003.http")
|
||||||
def test_003(buf, p):
|
def test_003(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "GET")
|
t.eq(p.method, "GET")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
t.eq(p.path, "/favicon.ico")
|
t.eq(p.path, "/favicon.ico")
|
||||||
@ -61,28 +61,28 @@ def test_003(buf, p):
|
|||||||
("Keep-Alive", "300"),
|
("Keep-Alive", "300"),
|
||||||
("User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"),
|
("User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0"),
|
||||||
])
|
])
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, "")
|
t.eq(body, "")
|
||||||
|
|
||||||
@t.request("004.http")
|
@t.request("004.http")
|
||||||
def test_004(buf, p):
|
def test_004(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "GET")
|
t.eq(p.method, "GET")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
t.eq(p.path, "/dumbfuck")
|
t.eq(p.path, "/dumbfuck")
|
||||||
t.eq(p.query_string, "")
|
t.eq(p.query_string, "")
|
||||||
t.eq(p.headers, [("Aaaaaaaaaaaaa", "++++++++++")])
|
t.eq(p.headers, [("Aaaaaaaaaaaaa", "++++++++++")])
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, "")
|
t.eq(body, "")
|
||||||
|
|
||||||
|
|
||||||
@t.request("005.http")
|
@t.request("005.http")
|
||||||
def test_005(buf, p):
|
def test_005(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "GET")
|
t.eq(p.method, "GET")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
t.eq(p.path, "/forums/1/topics/2375")
|
t.eq(p.path, "/forums/1/topics/2375")
|
||||||
@ -90,55 +90,55 @@ def test_005(buf, p):
|
|||||||
|
|
||||||
|
|
||||||
t.eq(p.fragment, "posts-17408")
|
t.eq(p.fragment, "posts-17408")
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, "")
|
t.eq(body, "")
|
||||||
|
|
||||||
@t.request("006.http")
|
@t.request("006.http")
|
||||||
def test_006(buf, p):
|
def test_006(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "GET")
|
t.eq(p.method, "GET")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
t.eq(p.path, "/get_no_headers_no_body/world")
|
t.eq(p.path, "/get_no_headers_no_body/world")
|
||||||
t.eq(p.query_string, "")
|
t.eq(p.query_string, "")
|
||||||
t.eq(p.fragment, "")
|
t.eq(p.fragment, "")
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, "")
|
t.eq(body, "")
|
||||||
|
|
||||||
@t.request("007.http")
|
@t.request("007.http")
|
||||||
def test_007(buf, p):
|
def test_007(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "GET")
|
t.eq(p.method, "GET")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
t.eq(p.path, "/get_one_header_no_body")
|
t.eq(p.path, "/get_one_header_no_body")
|
||||||
t.eq(p.query_string, "")
|
t.eq(p.query_string, "")
|
||||||
t.eq(p.fragment, "")
|
t.eq(p.fragment, "")
|
||||||
t.eq(p.headers, [('Accept', '*/*')])
|
t.eq(p.headers, [('Accept', '*/*')])
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, "")
|
t.eq(body, "")
|
||||||
|
|
||||||
@t.request("008.http")
|
@t.request("008.http")
|
||||||
def test_008(buf, p):
|
def test_008(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "GET")
|
t.eq(p.method, "GET")
|
||||||
t.eq(p.version, (1, 0))
|
t.eq(p.version, (1, 0))
|
||||||
t.eq(p.path, "/get_funky_content_length_body_hello")
|
t.eq(p.path, "/get_funky_content_length_body_hello")
|
||||||
t.eq(p.query_string, "")
|
t.eq(p.query_string, "")
|
||||||
t.eq(p.fragment, "")
|
t.eq(p.fragment, "")
|
||||||
t.eq(p.headers, [('Content-Length', '5')])
|
t.eq(p.headers, [('Content-Length', '5')])
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, "HELLO")
|
t.eq(body, "HELLO")
|
||||||
|
|
||||||
@t.request("009.http")
|
@t.request("009.http")
|
||||||
def test_009(buf, p):
|
def test_009(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "POST")
|
t.eq(p.method, "POST")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
t.eq(p.path, "/post_identity_body_world")
|
t.eq(p.path, "/post_identity_body_world")
|
||||||
@ -149,14 +149,14 @@ def test_009(buf, p):
|
|||||||
('Content-Length', '5'),
|
('Content-Length', '5'),
|
||||||
('Transfer-Encoding', 'identity')
|
('Transfer-Encoding', 'identity')
|
||||||
])
|
])
|
||||||
body, tr = p.filter_body(buf[i:])
|
body, tr = p.filter_body(buf2)
|
||||||
t.eq(body, "World")
|
t.eq(body, "World")
|
||||||
|
|
||||||
@t.request("010.http")
|
@t.request("010.http")
|
||||||
def test_010(buf, p):
|
def test_010(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "POST")
|
t.eq(p.method, "POST")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
t.eq(p.path, "/post_chunked_all_your_base")
|
t.eq(p.path, "/post_chunked_all_your_base")
|
||||||
@ -165,9 +165,8 @@ def test_010(buf, p):
|
|||||||
t.eq(p._chunk_eof, False)
|
t.eq(p._chunk_eof, False)
|
||||||
t.ne(p.body_eof(), True)
|
t.ne(p.body_eof(), True)
|
||||||
body = ""
|
body = ""
|
||||||
buf = buf[i:]
|
|
||||||
while not p.body_eof():
|
while not p.body_eof():
|
||||||
chunk, buf = p.filter_body(buf)
|
chunk, buf2 = p.filter_body(buf2)
|
||||||
print chunk
|
print chunk
|
||||||
if chunk:
|
if chunk:
|
||||||
body += chunk
|
body += chunk
|
||||||
@ -176,8 +175,8 @@ def test_010(buf, p):
|
|||||||
@t.request("011.http")
|
@t.request("011.http")
|
||||||
def test_011(buf, p):
|
def test_011(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "POST")
|
t.eq(p.method, "POST")
|
||||||
t.eq(p.version, (1, 1))
|
t.eq(p.version, (1, 1))
|
||||||
t.eq(p.path, "/two_chunks_mult_zero_end")
|
t.eq(p.path, "/two_chunks_mult_zero_end")
|
||||||
@ -186,18 +185,17 @@ def test_011(buf, p):
|
|||||||
t.eq(p._chunk_eof, False)
|
t.eq(p._chunk_eof, False)
|
||||||
t.ne(p.body_eof(), True)
|
t.ne(p.body_eof(), True)
|
||||||
body = ""
|
body = ""
|
||||||
buf = buf[i:]
|
|
||||||
while not p.body_eof():
|
while not p.body_eof():
|
||||||
chunk, buf = p.filter_body(buf)
|
chunk, buf2 = p.filter_body(buf2)
|
||||||
if chunk:
|
if chunk:
|
||||||
body += chunk
|
body += chunk
|
||||||
t.eq(body, "hello world")
|
t.eq(body, "hello world")
|
||||||
|
|
||||||
@t.request("017.http")
|
@t.request("017.http")
|
||||||
def test_013(buf, p):
|
def test_012(buf, p):
|
||||||
headers = []
|
headers = []
|
||||||
i = p.filter_headers(headers, buf)
|
buf2 = p.filter_headers(headers, buf)
|
||||||
t.ne(i, -1)
|
t.ne(buf2, False)
|
||||||
t.eq(p.method, "GET")
|
t.eq(p.method, "GET")
|
||||||
t.eq(p.version, (1, 0))
|
t.eq(p.version, (1, 0))
|
||||||
t.eq(p.path, "/stuff/here")
|
t.eq(p.path, "/stuff/here")
|
||||||
|
|||||||
11
tests/t.py
11
tests/t.py
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import array
|
import array
|
||||||
import os
|
import os
|
||||||
|
from StringIO import StringIO
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
dirname = os.path.dirname(__file__)
|
dirname = os.path.dirname(__file__)
|
||||||
@ -15,12 +16,12 @@ from gunicorn.http.request import Request
|
|||||||
from gunicorn.config import Config
|
from gunicorn.config import Config
|
||||||
|
|
||||||
def data_source(fname):
|
def data_source(fname):
|
||||||
|
buf = StringIO()
|
||||||
with open(fname) as handle:
|
with open(fname) as handle:
|
||||||
lines = []
|
|
||||||
for line in handle:
|
for line in handle:
|
||||||
line = line.rstrip("\n").replace("\\r\\n", "\r\n")
|
line = line.rstrip("\n").replace("\\r\\n", "\r\n")
|
||||||
lines.append(line)
|
buf.write(line)
|
||||||
return "".join(lines)
|
return buf
|
||||||
|
|
||||||
class request(object):
|
class request(object):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
@ -36,10 +37,10 @@ class request(object):
|
|||||||
|
|
||||||
class FakeSocket(object):
|
class FakeSocket(object):
|
||||||
|
|
||||||
def __init__(self, data=""):
|
def __init__(self, data):
|
||||||
self.tmp = tempfile.TemporaryFile()
|
self.tmp = tempfile.TemporaryFile()
|
||||||
if data:
|
if data:
|
||||||
self.tmp.write(data)
|
self.tmp.write(data.getvalue())
|
||||||
self.tmp.flush()
|
self.tmp.flush()
|
||||||
self.tmp.seek(0)
|
self.tmp.seek(0)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user