diff --git a/gunicorn/http/iostream.py b/gunicorn/http/iostream.py new file mode 100644 index 00000000..68ff780f --- /dev/null +++ b/gunicorn/http/iostream.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2008,2009 Benoit Chesneau +# +# 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. + +from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \ + ENOTCONN, ESHUTDOWN, EINTR, EISCONN, EBADF, ECONNABORTED, errorcode + +import socket + +class IOStream(object): + + chunk_size = 4096 + + def __init__(self, sock): + self.sock = sock + + self.buf = "" + + + def recv(self, buffer_size): + try: + data = self.sock.recv(buffer_size) + if not data: + # we should handle close here + return '' + return data + except socket.error, e: + if e.args[0] in (errno.ECONNRESET, errno.ENOTCONN, + errno.ESHUTDOWN, errno.ECONNABORTED): + # we should handle close here + return '' + raise + + def send(self, data): + try: + rst = self.sock.send(data) + return rst + except socket.error, e: + if e.args[0] == EWOULDBLOCK: + return 0 + elif e.args[0] in (errno.ECONNRESET, errno.ENOTCONN, + errno.ESHUTDOWN, errno.ECONNABORTED): + # we should handle close here + + return 0 + else: + raise + + def read_until(self, delimiter): + while True: + try: + data = self.recv(self.chunk_size) + except socket.error, e: + return + self.buf = self.buf + data + + lb = len(self.buf) + ld = len(delimiter) + i = self.buf.find(delimiter) + if i != -1: + if i > 0: + r = self.buf[:i] + self.buf = self.buf[i+ ld:] + return r diff --git a/gunicorn/http/request.py b/gunicorn/http/request.py index 0a41cfde..83a1646f 100644 --- a/gunicorn/http/request.py +++ b/gunicorn/http/request.py @@ -20,6 +20,7 @@ import sys from urllib import unquote from gunicorn import __version__ +from gunicorn.http.iostream import IOStream NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+') @@ -49,15 +50,13 @@ class HTTPRequest(object): self.response_status = None self.response_headers = {} self._version = 11 - self.fp = socket.makefile("rw", self.CHUNK_SIZE) + self.io = IOStream(socket) def read(self): - # get status line - self.first_line(self.fp.readline()) - + # read headers - self.read_headers() + self.read_headers(first_line=True) if "?" in self.path: path_info, query = self.path.split('?', 1) @@ -103,17 +102,13 @@ class HTTPRequest(object): environ[key] = value return environ - def read_headers(self): + def read_headers(self, first_line=False): + headers_str = self.io.read_until("\r\n\r\n") + lines = headers_str.split("\r\n") + self.first_line(lines.pop(0)) hname = "" - while True: - line = self.fp.readline() - - if line == "\r\n": - # end of headers - break - + for line in lines: if line == "\t": - # It's a continuation line. self.headers[hname] += line.strip() else: try: @@ -147,13 +142,13 @@ class HTTPRequest(object): length = 0 data = StringIO.StringIO() while True: - line = self.fp.readline().strip().split(";", 1) + line = self.io.readuntil("\n").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) + data.write(self.io.recv(chunk_size)) + crlf = self.io.read(2) if crlf != "\r\n": raise RequestError((400, "Bad chunked transfer coding " "(expected '\\r\\n', got %r)" % crlf)) @@ -173,13 +168,12 @@ class HTTPRequest(object): for name, value in response_headers: resp_head.append("%s: %s" % (name, value)) self.response_headers[name.lower()] = value - self.fp.write("%s\r\n\r\n" % "\r\n".join(resp_head)) + self.io.send("%s\r\n\r\n" % "\r\n".join(resp_head)) def write(self, data): - self.fp.write(data) + self.io.write(send) def close(self): - self.fp.close() if self.should_close(): self.socket.close() @@ -199,57 +193,49 @@ class HTTPRequest(object): class FileInput(object): + stream_size = 4096 + def __init__(self, req): self.length = req.body_length() - self.fp = req.fp - self.eof = False + self.io = req.io + self._rbuf = "" 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() + if self._rbuf and not amt is None: + L = len(self._rbuf) + if amt > L: + amt -= L + else: + s = self._rbuf[:amt] + self._rbuf = self._rbuf[amt:] + return s + data = self.io.recv(amt) + s = self._rbuf + data + self._rbuf = '' 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() + def readline(self, amt=-1): + i = self._rbuf.find('\n') + while i < 0 and not (0 < amt <= len(self._rbuf)): + new = self.io.recv(self.stream_size) + if not new: break + i = new.find('\n') + if i >= 0: + i = i + len(self._rbuf) + self._rbuf = self._rbuf + new + if i < 0: + i = len(self._rbuf) + else: + i = i+1 + if 0 <= amt < len(self._rbuf): + i = amt + data, self._rbuf = self._rbuf[:i], self._rbuf[i:] return data - + def readlines(self, sizehint=0): - # Shamelessly stolen from StringIO total = 0 lines = [] line = self.readline() @@ -261,27 +247,14 @@ class FileInput(object): line = self.readline() return lines + def next(self): + r = self.readline() + if not r: + raise StopIteration + return r - def _safe_read(self, amt): - """Read the number of bytes requested, compensating for partial reads. - """ - s = [] - while amt > 0: - chunk = self.fp.read(amt) - if not chunk: - raise RequestError(500, "Incomplete read %s" % 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() - \ No newline at end of file diff --git a/gunicorn/http/response.py b/gunicorn/http/response.py index fb1211c5..04eea89a 100644 --- a/gunicorn/http/response.py +++ b/gunicorn/http/response.py @@ -21,13 +21,12 @@ class HTTPResponse(object): self.req = req self.data = data self.headers = self.req.response_headers or {} - self.fp = req.fp + self.io = req.io def write(self, data): - self.fp.write(data) + self.io.send(data) def send(self): if not self.data: return for chunk in self.data: - self.write(chunk) - self.fp.flush() \ No newline at end of file + self.write(chunk) \ No newline at end of file