Rev 441: include HeadMiddleware so that we can be sure HEAD requests never return BODY content. in http://bazaar.launchpad.net/~loggerhead-team/loggerhead/trunk-rich
John Arbash Meinel
john at arbash-meinel.com
Sat Mar 19 08:36:23 UTC 2011
At http://bazaar.launchpad.net/~loggerhead-team/loggerhead/trunk-rich
------------------------------------------------------------
revno: 441 [merge]
revision-id: john at arbash-meinel.com-20110319083557-k8mbbkr3bzisz3ob
parent: john at arbash-meinel.com-20110318093901-56t8eepvwp2avnaz
parent: john at arbash-meinel.com-20110210023315-515pkynlfpfs3cvm
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: trunk-rich
timestamp: Sat 2011-03-19 09:35:57 +0100
message:
include HeadMiddleware so that we can be sure HEAD requests never return BODY content.
added:
loggerhead/apps/http_head.py http_head.py-20110210021453-bsk2uowiyifp730j-1
loggerhead/tests/test_http_head.py test_http_head.py-20110210021453-bsk2uowiyifp730j-2
modified:
NEWS news-20070121024650-6cwmhprgtcegpxvm-1
__init__.py __init__.py-20090123174919-ekabddqbmvwuci2i-1
loggerhead/tests/__init__.py __init__.py-20061211064342-102iqirsciyvgtcf-29
loggerhead/tests/test_simple.py test_simple.py-20070523103003-hrxwg5n1hcebw7l6-1
-------------- next part --------------
=== modified file 'NEWS'
--- a/NEWS 2011-03-16 14:43:36 +0000
+++ b/NEWS 2011-03-19 08:35:57 +0000
@@ -9,6 +9,12 @@
multiple threads, and issue concurrent requests.
(John Arbash Meinel)
+ - HEAD requests should not return body content. This is done by adding
+ another wsgi middleware that strips the body when the REQUEST_METHOD is
+ HEAD. Note that you have to add the middleware into your pipeline, and
+ it does not decrease the actual work done.
+ (John Arbash Meinel, #716201)
+
- If we get a HEAD request, there is no reason to expand the template, we
shouldn't be returning body content anyway.
(John Arbash Meinel, #716201, #716217)
=== modified file '__init__.py'
--- a/__init__.py 2011-03-16 12:29:36 +0000
+++ b/__init__.py 2011-03-19 08:35:57 +0000
@@ -62,11 +62,16 @@
sys.path.append(os.path.dirname(__file__))
def serve_http(transport, host=None, port=None, inet=None):
+ # TODO: if we supported inet to pass requests in and respond to them,
+ # then it would be easier to test the full stack, but it probably
+ # means routing around paste.httpserver.serve which probably
+ # isn't testing the full stack
from paste.httpexceptions import HTTPExceptionHandler
from paste.httpserver import serve
_ensure_loggerhead_path()
+ from loggerhead.apps.http_head import HeadMiddleware
from loggerhead.apps.transport import BranchesFromTransportRoot
from loggerhead.config import LoggerheadConfig
from loggerhead.main import setup_logging
@@ -81,6 +86,7 @@
config = LoggerheadConfig(argv)
setup_logging(config, init_logging=False, log_file=sys.stderr)
app = BranchesFromTransportRoot(transport.base, config)
+ app = HeadMiddleware(app)
app = HTTPExceptionHandler(app)
serve(app, host=host, port=port)
=== added file 'loggerhead/apps/http_head.py'
--- a/loggerhead/apps/http_head.py 1970-01-01 00:00:00 +0000
+++ b/loggerhead/apps/http_head.py 2011-02-10 02:33:15 +0000
@@ -0,0 +1,62 @@
+# Copyright (C) 2011 Canonical Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""WSGI apps tend to return body content as part of a HEAD request.
+
+We should definitely not do that.
+"""
+
+class HeadMiddleware(object):
+ """When we get a HEAD request, we should not return body content.
+
+ WSGI defaults to just generating everything, and not paying attention to
+ whether it is a GET or a HEAD request. It does that because of potential
+ issues getting the Headers correct.
+
+ This middleware works by just omitting the body if the request method is
+ HEAD.
+ """
+
+ def __init__(self, app):
+ self._wrapped_app = app
+ self._real_environ = None
+ self._real_start_response = None
+ self._real_writer = None
+
+ def noop_write(self, chunk):
+ """We intentionally ignore all body content that is returned."""
+ pass
+
+ def start_response(self, status, response_headers, exc_info=None):
+ if exc_info is None:
+ self._real_writer = self._real_start_response(status,
+ response_headers)
+ else:
+ self._real_writer = self._real_start_response(status,
+ response_headers, exc_info)
+ return self.noop_write
+
+ def __call__(self, environ, start_response):
+ self._real_environ = environ
+ self._real_start_response = start_response
+ if environ.get('REQUEST_METHOD', 'GET') == 'HEAD':
+ result = self._wrapped_app(environ, self.start_response)
+ for chunk in result:
+ pass
+ else:
+ result = self._wrapped_app(environ, start_response)
+ for chunk in result:
+ yield chunk
=== modified file 'loggerhead/tests/__init__.py'
--- a/loggerhead/tests/__init__.py 2011-03-16 14:43:36 +0000
+++ b/loggerhead/tests/__init__.py 2011-03-19 08:35:57 +0000
@@ -21,6 +21,7 @@
'test_controllers',
'test_corners',
'test_history',
+ 'test_http_head',
'test_load_test',
'test_simple',
'test_revision_ui',
=== added file 'loggerhead/tests/test_http_head.py'
--- a/loggerhead/tests/test_http_head.py 1970-01-01 00:00:00 +0000
+++ b/loggerhead/tests/test_http_head.py 2011-02-10 02:33:15 +0000
@@ -0,0 +1,92 @@
+# Copyright 2011 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""Tests for the HeadMiddleware app."""
+
+from cStringIO import StringIO
+
+from bzrlib import tests
+
+from loggerhead.apps import http_head
+
+
+content = ["<html>",
+ "<head><title>Listed</title></head>",
+ "<body>Content</body>",
+ "</html>",
+ ]
+headers = {'X-Ignored-Header': 'Value'}
+
+def yielding_app(environ, start_response):
+ writer = start_response('200 OK', headers)
+ for chunk in content:
+ yield chunk
+
+
+def list_app(environ, start_response):
+ writer = start_response('200 OK', headers)
+ return content
+
+
+def writer_app(environ, start_response):
+ writer = start_response('200 OK', headers)
+ for chunk in content:
+ writer(chunk)
+ return []
+
+
+class TestHeadMiddleware(tests.TestCase):
+
+ def _trap_start_response(self, status, response_headers, exc_info=None):
+ self._write_buffer = StringIO()
+ self._start_response_passed = (status, response_headers, exc_info)
+ return self._write_buffer.write
+
+ def _consume_app(self, app, request_method):
+ environ = {'REQUEST_METHOD': request_method}
+ value = list(app(environ, self._trap_start_response))
+ self._write_buffer.writelines(value)
+
+ def _verify_get_passthrough(self, app):
+ app = http_head.HeadMiddleware(app)
+ self._consume_app(app, 'GET')
+ self.assertEqual(('200 OK', headers, None), self._start_response_passed)
+ self.assertEqualDiff(''.join(content), self._write_buffer.getvalue())
+
+ def _verify_head_no_body(self, app):
+ app = http_head.HeadMiddleware(app)
+ self._consume_app(app, 'HEAD')
+ self.assertEqual(('200 OK', headers, None), self._start_response_passed)
+ self.assertEqualDiff('', self._write_buffer.getvalue())
+
+ def test_get_passthrough_yielding(self):
+ self._verify_get_passthrough(yielding_app)
+
+ def test_head_passthrough_yielding(self):
+ self._verify_head_no_body(yielding_app)
+
+ def test_get_passthrough_list(self):
+ self._verify_get_passthrough(list_app)
+
+ def test_head_passthrough_list(self):
+ self._verify_head_no_body(list_app)
+
+ def test_get_passthrough_writer(self):
+ self._verify_get_passthrough(writer_app)
+
+ def test_head_passthrough_writer(self):
+ self._verify_head_no_body(writer_app)
+
=== modified file 'loggerhead/tests/test_simple.py'
--- a/loggerhead/tests/test_simple.py 2011-03-16 14:43:36 +0000
+++ b/loggerhead/tests/test_simple.py 2011-03-19 08:35:57 +0000
@@ -27,6 +27,7 @@
from bzrlib import config
from loggerhead.apps.branch import BranchWSGIApp
+from loggerhead.apps.http_head import HeadMiddleware
from paste.fixture import TestApp
from paste.httpexceptions import HTTPExceptionHandler, HTTPMovedPermanently
@@ -197,6 +198,32 @@
e = self.assertRaises(HTTPMovedPermanently, app.get, '/files/head:/file')
self.assertEqual(e.location(), '/view/head:/file')
+
+class TestHeadMiddleware(BasicTests):
+
+ def setUp(self):
+ BasicTests.setUp(self)
+ self.createBranch()
+ self.msg = 'trivial commit message'
+ self.revid = self.tree.commit(message=self.msg)
+
+ def setUpLoggerhead(self, **kw):
+ branch_app = BranchWSGIApp(self.tree.branch, '', **kw).app
+ return TestApp(HTTPExceptionHandler(HeadMiddleware(branch_app)))
+
+ def test_get(self):
+ app = self.setUpLoggerhead()
+ res = app.get('/changes')
+ res.mustcontain(self.msg)
+ self.assertEqual('text/html', res.header('Content-Type'))
+
+ def test_head(self):
+ app = self.setUpLoggerhead()
+ res = app.get('/changes', extra_environ={'REQUEST_METHOD': 'HEAD'})
+ self.assertEqual('text/html', res.header('Content-Type'))
+ self.assertEqualDiff('', res.body)
+
+
#class TestGlobalConfig(BasicTests):
# """
# Test that global config settings are respected
More information about the bazaar-commits
mailing list