diff --git a/gunicorn/http/__init__.py b/gunicorn/http/__init__.py index 10e2691f..61936ab8 100644 --- a/gunicorn/http/__init__.py +++ b/gunicorn/http/__init__.py @@ -4,5 +4,5 @@ # See the NOTICE for more information. from gunicorn.http.http_parser import HttpParser -from gunicorn.http.request import HTTPRequest, RequestError -from gunicorn.http.response import HTTPResponse \ No newline at end of file +from gunicorn.http.request import HttpRequest, RequestError +from gunicorn.http.response import HttpResponse \ No newline at end of file diff --git a/gunicorn/http/http_parser.py b/gunicorn/http/http_parser.py index 4a1f37d2..effb90ad 100644 --- a/gunicorn/http/http_parser.py +++ b/gunicorn/http/http_parser.py @@ -16,9 +16,9 @@ class HttpParser(object): self.status = "" self.headers = [] self.headers_dict = {} - self.raw_version = "" + self.raw_version = "HTTP/1.0" self.raw_path = "" - self.version = None + self.version = (1,0) self.method = "" self.path = "" self.query_string = "" @@ -70,8 +70,7 @@ class HttpParser(object): self.headers_dict = _headers headers.extend(list(_headers.items())) self.headers = headers - self._content_len = int(_headers.get('Content-Length') or 0) - + self._content_len = int(_headers.get('Content-Length',0)) (_, _, self.path, self.query_string, self.fragment) = urlparse.urlsplit(self.raw_path) return pos @@ -122,7 +121,7 @@ class HttpParser(object): None.""" transfert_encoding = self.headers_dict.get('Transfer-Encoding') content_length = self.headers_dict.get('Content-Length') - if transfert_encoding is None: + if transfert_encoding != "chunked": if content_length is None: return 0 return int(content_length) diff --git a/gunicorn/http/request.py b/gunicorn/http/request.py index e5bbb8d9..275ad6a6 100644 --- a/gunicorn/http/request.py +++ b/gunicorn/http/request.py @@ -30,7 +30,7 @@ class RequestError(Exception): """ raised when something wrong happend""" -class HTTPRequest(object): +class HttpRequest(object): SERVER_VERSION = "gunicorn/%s" % __version__ @@ -74,9 +74,6 @@ class HTTPRequest(object): i = self.parser.filter_headers(headers, buf) if i != -1: break - if not headers: - environ.update(self.DEFAULTS) - return environ self.log.info("%s", self.parser.status) diff --git a/gunicorn/http/response.py b/gunicorn/http/response.py index b00482d0..0f7d3df0 100644 --- a/gunicorn/http/response.py +++ b/gunicorn/http/response.py @@ -11,7 +11,7 @@ import time import os from gunicorn.util import http_date, write, read_partial, close -class HTTPResponse(object): +class HttpResponse(object): def __init__(self, sock, response, req): self.req = req diff --git a/gunicorn/http/tee.py b/gunicorn/http/tee.py index 20c70b4d..6d59ce26 100644 --- a/gunicorn/http/tee.py +++ b/gunicorn/http/tee.py @@ -43,6 +43,7 @@ class TeeInput(object): if self._len: return self._len if self._is_socket: pos = self.tmp.tell() + print pos while True: if not self._tee(CHUNK_SIZE): break @@ -53,14 +54,13 @@ class TeeInput(object): def flush(self): self.tmp.flush() - def read(self, length=None): + def read(self, length=-1): """ read """ if not self._is_socket: return self.tmp.read(length) - if length is None: + if length < 0: r = self.tmp.read() or "" - print "avant %s" % str(len(r)) while True: chunk = self._tee(CHUNK_SIZE) if not chunk: break @@ -125,10 +125,7 @@ class TeeInput(object): if chunk: self.tmp.write(chunk) self.tmp.seek(0, os.SEEK_END) - return chunk - if not data: - self._is_socket = False - break + return chunk self._finalize() return "" diff --git a/gunicorn/worker.py b/gunicorn/worker.py index b6e35e05..2a1017fb 100644 --- a/gunicorn/worker.py +++ b/gunicorn/worker.py @@ -19,7 +19,6 @@ from gunicorn import http from gunicorn import util - class Worker(object): SIGNALS = map( @@ -126,9 +125,9 @@ class Worker(object): def handle(self, client, addr): self.close_on_exec(client) try: - req = http.HTTPRequest(client, addr, self.address) + req = http.HttpRequest(client, addr, self.address) response = self.app(req.read(), req.start_response) - http.HTTPResponse(client, response, req).send() + http.HttpResponse(client, response, req).send() except Exception, e: # TODO: try to send something if an error happend self.log.exception("Error processing request. [%s]" % str(e)) diff --git a/tests/001-test-parser.py b/tests/001-test-parser.py index 6912b75e..63d99939 100644 --- a/tests/001-test-parser.py +++ b/tests/001-test-parser.py @@ -22,7 +22,7 @@ def test_001(buf, p): ]) body, tr = p.filter_body(buf[i:]) t.eq(body, '{"nom": "nom"}') - print t.eq(p.body_eof(), True) + t.eq(p.body_eof(), True) @t.request("002.http") def test_002(buf, p): diff --git a/tests/002-test-request.py b/tests/002-test-request.py new file mode 100644 index 00000000..3fda0ff0 --- /dev/null +++ b/tests/002-test-request.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + + +import t + +from gunicorn.http import tee + +@t.http_request("001.http") +def test_001(req): + e = req.read() + t.eq(e['CONTENT_LENGTH'], '14') + t.eq(e['wsgi.version'], (1,0)) + t.eq(e['REQUEST_METHOD'], 'PUT') + t.eq(e['PATH_INFO'], '/stuff/here') + t.eq(e['CONTENT_TYPE'], 'application/json') + t.eq(e['QUERY_STRING'], 'foo=bar') + + t.eq(isinstance(e['wsgi.input'], tee.TeeInput), True) + body = e['wsgi.input'].read() + t.eq(body, '{"nom": "nom"}') + +@t.http_request("002.http") +def http_request(req): + e = req.read() + + t.eq(e['REQUEST_METHOD'], 'GET') + t.eq(e['PATH_INFO'], "/test") + t.eq(e['QUERY_STRING'], "") + t.eq(sorted(p.headers), [ + ("Accept", "*/*"), + ("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") + ]) + t.eq(e['HTTP_ACCEPT'], "*/*") + t.eq(e['HTTP_HOST'], "0.0.0.0=5000") + t.eq(e['HTTP_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 = e['wsgi.input'].read() + t.eq(body, '') + +@t.http_request("003.http") +def test_003(req): + e = req.read() + + t.eq(e['REQUEST_METHOD'], 'GET') + t.eq(e['PATH_INFO'], "/favicon.ico") + t.eq(e['QUERY_STRING'], "") + t.eq(e['HTTP_ACCEPT'], "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + t.eq(e['HTTP_KEEP_ALIVE'], "300") + + body = e['wsgi.input'].read() + t.eq(body, '') + +@t.http_request("004.http") +def test_004(req): + e = req.read() + t.eq(e['REQUEST_METHOD'], 'GET') + t.eq(e['PATH_INFO'], "/dumbfuck") + t.eq(e['QUERY_STRING'], "") + body = e['wsgi.input'].read() + t.eq(body, '') + + +@t.http_request("005.http") +def test_005(req): + e = req.read() + t.eq(e['REQUEST_METHOD'], 'GET') + t.eq(e['PATH_INFO'], "/forums/1/topics/2375") + t.eq(e['QUERY_STRING'], "page=1") + body = e['wsgi.input'].read() + t.eq(body, '') + + +@t.http_request("006.http") +def test_006(req): + e = req.read() + t.eq(e['REQUEST_METHOD'], 'GET') + t.eq(e['PATH_INFO'], "/get_no_headers_no_body/world") + t.eq(e['QUERY_STRING'], "") + body = e['wsgi.input'].read() + t.eq(body, '') + + +@t.http_request("007.http") +def test_007(req): + e = req.read() + t.eq(e['REQUEST_METHOD'], 'GET') + t.eq(e['PATH_INFO'], "/get_one_header_no_body") + t.eq(e['QUERY_STRING'], "") + t.eq(e['HTTP_ACCEPT'], "*/*") + body = e['wsgi.input'].read() + t.eq(body, '') + + +@t.http_request("008.http") +def test_008(req): + e = req.read() + t.eq(e['REQUEST_METHOD'], 'GET') + t.eq(e['PATH_INFO'], "/get_funky_content_length_body_hello") + t.eq(e['QUERY_STRING'], "") + t.eq(e['CONTENT_LENGTH'], '5') + body = e['wsgi.input'].read() + t.eq(body, "HELLO") + + +@t.http_request("009.http") +def test_009(req): + e = req.read() + t.eq(e['REQUEST_METHOD'], 'POST') + t.eq(e['PATH_INFO'], "/post_identity_body_world") + t.eq(e['QUERY_STRING'], "q=search") + t.eq(e['CONTENT_LENGTH'], '5') + body = e['wsgi.input'].read() + t.eq(body, "World") + + +@t.http_request("010.http") +def test_010(req): + e = req.read() + t.eq(e['REQUEST_METHOD'], 'POST') + t.eq(e['PATH_INFO'], "/post_chunked_all_your_base") + t.eq(e['HTTP_TRANSFER_ENCODING'], "chunked") + t.eq(e['CONTENT_LENGTH'], '30') + body = e['wsgi.input'].read() + t.eq(body, "all your base are belong to us") + + +@t.http_request("011.http") +def test_011(req): + e = req.read() + t.eq(e['REQUEST_METHOD'], 'POST') + t.eq(e['PATH_INFO'], "/two_chunks_mult_zero_end") + t.eq(e['HTTP_TRANSFER_ENCODING'], "chunked") + t.eq(e['CONTENT_LENGTH'], '11') + body = e['wsgi.input'].read() + t.eq(body, "hello world") + \ No newline at end of file diff --git a/tests/t.py b/tests/t.py index beaacd98..50a65238 100644 --- a/tests/t.py +++ b/tests/t.py @@ -7,14 +7,15 @@ import inspect import os import re +import tempfile import unittest -from gunicorn.http import HttpParser - dirname = os.path.dirname(__file__) +from gunicorn.http.http_parser import HttpParser +from gunicorn.http.request import HttpRequest -def data_source(fname, eol): +def data_source(fname): with open(fname) as handle: lines = [] for line in handle: @@ -23,16 +24,56 @@ def data_source(fname, eol): return "".join(lines) class request(object): - def __init__(self, name, eol="\r\n"): + def __init__(self, name): self.fname = os.path.join(dirname, "requests", name) - self.eol = eol def __call__(self, func): def run(): - src = data_source(self.fname, self.eol) + src = data_source(self.fname) func(src, HttpParser()) run.func_name = func.func_name return run + + +class FakeSocket(object): + + def __init__(self, data): + self.tmp = tempfile.TemporaryFile() + self.tmp.write(data) + self.tmp.flush() + self.tmp.seek(0) + + def fileno(self): + return self.tmp.fileno() + + def len(self): + return self.tmp.len + + def recv(self, length=None): + return self.tmp.read() + + def seek(self, offset, whence=0): + self.tmp.seek(offset, whence) + + +class http_request(object): + def __init__(self, name): + self.fname = os.path.join(dirname, "requests", name) + + def __call__(self, func): + def run(): + fsock = FakeSocket(data_source(self.fname)) + + + req = HttpRequest(fsock, ('127.0.0.1', 6000), + ('127.0.0.1', 8000)) + func(req) + run.func_name = func.func_name + return run + + + + def eq(a, b): assert a == b, "%r != %r" % (a, b)