Merge lp:~jelmer/dulwich/gzip-filter into lp:~vcs-imports/dulwich/trunk

Proposed by Jelmer Vernooij
Status: Merged
Merge reported by: Jelmer Vernooij
Merged at revision: not available
Proposed branch: lp:~jelmer/dulwich/gzip-filter
Merge into: lp:~vcs-imports/dulwich/trunk
Diff against target: 242 lines (+123/-30)
4 files modified
NEWS (+4/-0)
dulwich/tests/compat/test_web.py (+8/-3)
dulwich/tests/test_web.py (+60/-16)
dulwich/web.py (+51/-11)
To merge this branch: bzr merge lp:~jelmer/dulwich/gzip-filter
Reviewer Review Type Date Requested Status
VCS imports Pending
Review via email: mp+96960@code.launchpad.net

Description of the change

Add a gzip filter, based on David Blewetts patch.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2012-03-03 18:39:49 +0000
3+++ NEWS 2012-03-12 03:10:22 +0000
4@@ -22,6 +22,10 @@
5 * Additional arguments to get_transport_and_path are now passed
6 on to the constructor of the transport. (Sam Vilain)
7
8+ * The WSGI server now transparently handles when a git client submits data
9+ using Content-Encoding: gzip.
10+ (David Blewett, Jelmer Vernooij)
11+
12 0.8.3 2012-01-21
13
14 FEATURES
15
16=== modified file 'dulwich/tests/compat/test_web.py'
17--- dulwich/tests/compat/test_web.py 2011-09-03 14:55:47 +0000
18+++ dulwich/tests/compat/test_web.py 2012-03-12 03:10:22 +0000
19@@ -34,6 +34,7 @@
20 SkipTest,
21 )
22 from dulwich.web import (
23+ make_wsgi_chain,
24 HTTPGitApplication,
25 HTTPGitRequestHandler,
26 )
27@@ -101,8 +102,12 @@
28 self.assertFalse('side-band-64k' in caps)
29
30 def _make_app(self, backend):
31- app = HTTPGitApplication(backend, handlers=self._handlers())
32- self._check_app(app)
33+ app = make_wsgi_chain(backend, handlers=self._handlers())
34+ to_check = app
35+ # peel back layers until we're at the base application
36+ while not issubclass(to_check.__class__, HTTPGitApplication):
37+ to_check = to_check.app
38+ self._check_app(to_check)
39 return app
40
41
42@@ -125,7 +130,7 @@
43 """Test cases for dumb HTTP server."""
44
45 def _make_app(self, backend):
46- return HTTPGitApplication(backend, dumb=True)
47+ return make_wsgi_chain(backend, dumb=True)
48
49 def test_push_to_dulwich(self):
50 # Note: remove this if dumb pushing is supported
51
52=== modified file 'dulwich/tests/test_web.py'
53--- dulwich/tests/test_web.py 2012-03-11 02:01:52 +0000
54+++ dulwich/tests/test_web.py 2012-03-12 03:10:22 +0000
55@@ -19,6 +19,7 @@
56 """Tests for the Git HTTP server."""
57
58 from cStringIO import StringIO
59+import gzip
60 import re
61 import os
62
63@@ -44,6 +45,7 @@
64 HTTP_NOT_FOUND,
65 HTTP_FORBIDDEN,
66 HTTP_ERROR,
67+ GunzipFilter,
68 send_file,
69 get_text_file,
70 get_loose_object,
71@@ -419,19 +421,61 @@
72 super(HTTPGitApplicationTestCase, self).setUp()
73 self._app = HTTPGitApplication('backend')
74
75- def test_call(self):
76- def test_handler(req, backend, mat):
77- # tests interface used by all handlers
78- self.assertEqual(environ, req.environ)
79- self.assertEqual('backend', backend)
80- self.assertEqual('/foo', mat.group(0))
81- return 'output'
82-
83- self._app.services = {
84- ('GET', re.compile('/foo$')): test_handler,
85- }
86- environ = {
87- 'PATH_INFO': '/foo',
88- 'REQUEST_METHOD': 'GET',
89- }
90- self.assertEqual('output', self._app(environ, None))
91+ self._environ = {
92+ 'PATH_INFO': '/foo',
93+ 'REQUEST_METHOD': 'GET',
94+ }
95+
96+ def _test_handler(self, req, backend, mat):
97+ # tests interface used by all handlers
98+ self.assertEquals(self._environ, req.environ)
99+ self.assertEquals('backend', backend)
100+ self.assertEquals('/foo', mat.group(0))
101+ return 'output'
102+
103+ def _add_handler(self, app):
104+ req = self._environ['REQUEST_METHOD']
105+ app.services = {
106+ (req, re.compile('/foo$')): self._test_handler,
107+ }
108+
109+ def test_call(self):
110+ self._add_handler(self._app)
111+ self.assertEquals('output', self._app(self._environ, None))
112+
113+
114+class GunzipTestCase(HTTPGitApplicationTestCase):
115+ """TestCase for testing the GunzipFilter, ensuring the wsgi.input
116+ is correctly decompressed and headers are corrected.
117+ """
118+
119+ def setUp(self):
120+ super(GunzipTestCase, self).setUp()
121+ self._app = GunzipFilter(self._app)
122+ self._environ['HTTP_CONTENT_ENCODING'] = 'gzip'
123+ self._environ['REQUEST_METHOD'] = 'POST'
124+
125+ def _get_zstream(self, text):
126+ zstream = StringIO()
127+ zfile = gzip.GzipFile(fileobj=zstream, mode='w')
128+ zfile.write(text)
129+ zfile.close()
130+ return zstream
131+
132+ def test_call(self):
133+ self._add_handler(self._app.app)
134+ orig = self.__class__.__doc__
135+ zstream = self._get_zstream(orig)
136+ zlength = zstream.tell()
137+ zstream.seek(0)
138+ self.assertLess(zlength, len(orig))
139+ self.assertEquals(self._environ['HTTP_CONTENT_ENCODING'], 'gzip')
140+ self._environ['CONTENT_LENGTH'] = zlength
141+ self._environ['wsgi.input'] = zstream
142+ app_output = self._app(self._environ, None)
143+ buf = self._environ['wsgi.input']
144+ self.assertIsNot(buf, zstream)
145+ buf.seek(0)
146+ self.assertEquals(orig, buf.read())
147+ self.assertIs(None, self._environ.get('CONTENT_LENGTH'))
148+ self.assertNotIn('HTTP_CONTENT_ENCODING', self._environ)
149
150=== modified file 'dulwich/web.py'
151--- dulwich/web.py 2011-12-18 20:30:23 +0000
152+++ dulwich/web.py 2012-03-12 03:10:22 +0000
153@@ -19,6 +19,7 @@
154 """HTTP server for dulwich that implements the git smart HTTP protocol."""
155
156 from cStringIO import StringIO
157+import gzip
158 import os
159 import re
160 import sys
161@@ -225,16 +226,7 @@
162 return
163 req.nocache()
164 write = req.respond(HTTP_OK, 'application/x-%s-result' % service)
165-
166- input = req.environ['wsgi.input']
167- # This is not necessary if this app is run from a conforming WSGI server.
168- # Unfortunately, there's no way to tell that at this point.
169- # TODO: git may used HTTP/1.1 chunked encoding instead of specifying
170- # content-length
171- content_length = req.environ.get('CONTENT_LENGTH', '')
172- if content_length:
173- input = _LengthLimitedFile(input, int(content_length))
174- proto = ReceivableProtocol(input.read, write)
175+ proto = ReceivableProtocol(req.environ['wsgi.input'].read, write)
176 handler = handler_cls(backend, [url_prefix(mat)], proto, http_req=req)
177 handler.handle()
178
179@@ -352,6 +344,54 @@
180 return handler(req, self.backend, mat)
181
182
183+class GunzipFilter(object):
184+ """WSGI middleware that unzips gzip-encoded requests before
185+ passing on to the underlying application.
186+ """
187+
188+ def __init__(self, application):
189+ self.app = application
190+
191+ def __call__(self, environ, start_response):
192+ if environ.get('HTTP_CONTENT_ENCODING', '') == 'gzip':
193+ zlength = int(environ.get('CONTENT_LENGTH', '0'))
194+ environ.pop('HTTP_CONTENT_ENCODING')
195+ if 'CONTENT_LENGTH' in environ:
196+ del environ['CONTENT_LENGTH']
197+ environ['wsgi.input'] = gzip.GzipFile(filename=None,
198+ fileobj=environ['wsgi.input'], mode='r')
199+ return self.app(environ, start_response)
200+
201+
202+class LimitedInputFilter(object):
203+ """WSGI middleware that limits the input length of a request to that
204+ specified in Content-Length.
205+ """
206+
207+ def __init__(self, application):
208+ self.app = application
209+
210+ def __call__(self, environ, start_response):
211+ # This is not necessary if this app is run from a conforming WSGI
212+ # server. Unfortunately, there's no way to tell that at this point.
213+ # TODO: git may used HTTP/1.1 chunked encoding instead of specifying
214+ # content-length
215+ content_length = environ.get('CONTENT_LENGTH', '')
216+ if content_length:
217+ environ['wsgi.input'] = _LengthLimitedFile(
218+ environ['wsgi.input'], int(content_length))
219+ return self.app(environ, start_response)
220+
221+
222+def make_wsgi_chain(backend, dumb=False, handlers=None):
223+ """Factory function to create an instance of HTTPGitApplication,
224+ correctly wrapped with needed middleware.
225+ """
226+ app = HTTPGitApplication(backend, dumb, handlers)
227+ wrapped_app = GunzipFilter(LimitedInputFilter(app))
228+ return wrapped_app
229+
230+
231 # The reference server implementation is based on wsgiref, which is not
232 # distributed with python 2.4. If wsgiref is not present, users will not be able
233 # to use the HTTP server without a little extra work.
234@@ -388,7 +428,7 @@
235
236 log_utils.default_logging_config()
237 backend = DictBackend({'/': Repo(gitdir)})
238- app = HTTPGitApplication(backend)
239+ app = make_wsgi_chain(backend)
240 server = make_server(listen_addr, port, app,
241 handler_class=HTTPGitRequestHandler)
242 logger.info('Listening for HTTP connections on %s:%d', listen_addr,

Subscribers

People subscribed via source and target branches