web: Use write() callable for output.
authorDave Borowitz <dborowitz@google.com>
Mon, 26 Jul 2010 17:17:50 +0000 (19:17 +0200)
committerJelmer Vernooij <jelmer@samba.org>
Mon, 26 Jul 2010 17:17:50 +0000 (19:17 +0200)
PEP 333 specifies that start_response must return a write() callable
for compatibility with legacy stream-based APIs, which includes the
tunneled git protocol code.

NEWS
dulwich/tests/test_web.py
dulwich/web.py

diff --git a/NEWS b/NEWS
index c710b833894ff9d7afb0025608d531424cb6af8e..ec32c5f87f7d11144c0070a4cebf984d0dae76e7 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,9 @@
 
   * Use slots for core objects to save up on memory. (Jelmer Vernooij)
 
+  * Web server supports streaming progress/pack output. (Dave Borowitz)
+
+
 0.6.1  2010-07-22
 
  BUG FIXES
index 6a2f7c101920ffff5f35456388cf9d6135d976a9..9e5010a79251626a5b77f9a31379fcbb356c5776 100644 (file)
@@ -50,10 +50,12 @@ class WebTestCase(TestCase):
                                    handlers=self._handlers())
         self._status = None
         self._headers = []
+        self._output = StringIO()
 
     def _start_response(self, status, headers):
         self._status = status
         self._headers = list(headers)
+        return self._output.write
 
     def _handlers(self):
         return None
@@ -197,8 +199,12 @@ class SmartHandlersTestCase(WebTestCase):
         if content_length is not None:
             self._environ['CONTENT_LENGTH'] = content_length
         mat = re.search('.*', '/git-upload-pack')
-        output = ''.join(handle_service_request(self._req, 'backend', mat))
-        self.assertEqual('handled input: foo', output)
+        handler_output = ''.join(
+          handle_service_request(self._req, 'backend', mat))
+        write_output = self._output.getvalue()
+        # Ensure all output was written via the write callback.
+        self.assertEqual('', handler_output)
+        self.assertEqual('handled input: foo', write_output)
         response_type = 'application/x-git-upload-pack-response'
         self.assertTrue(('Content-Type', response_type) in self._headers)
         self.assertFalse(self._handler.advertise_refs)
@@ -223,11 +229,14 @@ class SmartHandlersTestCase(WebTestCase):
         self._environ['QUERY_STRING'] = 'service=git-upload-pack'
 
         mat = re.search('.*', '/git-upload-pack')
-        output = ''.join(get_info_refs(self._req, 'backend', mat))
+        handler_output = ''.join(get_info_refs(self._req, 'backend', mat))
+        write_output = self._output.getvalue()
         self.assertEquals(('001e# service=git-upload-pack\n'
                            '0000'
                            # input is ignored by the handler
-                           'handled input: '), output)
+                           'handled input: '), write_output)
+        # Ensure all output was written via the write callback.
+        self.assertEquals('', handler_output)
         self.assertTrue(self._handler.advertise_refs)
         self.assertTrue(self._handler.stateless_rpc)
 
index d7ac60b15e50199f9bf2a5aa22842ba74da237d0..d42bd6ac107d0ba953fcff30316695fe52f4af22 100644 (file)
@@ -159,15 +159,13 @@ def get_info_refs(req, backend, mat):
             yield req.forbidden('Unsupported service %s' % service)
             return
         req.nocache()
-        req.respond(HTTP_OK, 'application/x-%s-advertisement' % service)
-        output = StringIO()
-        proto = ReceivableProtocol(StringIO().read, output.write)
+        write = req.respond(HTTP_OK, 'application/x-%s-advertisement' % service)
+        proto = ReceivableProtocol(StringIO().read, write)
         handler = handler_cls(backend, [url_prefix(mat)], proto,
                               stateless_rpc=True, advertise_refs=True)
         handler.proto.write_pkt_line('# service=%s\n' % service)
         handler.proto.write_pkt_line(None)
         handler.handle()
-        yield output.getvalue()
     else:
         # non-smart fallback
         # TODO: select_getanyfile() (see http-backend.c)
@@ -230,9 +228,8 @@ def handle_service_request(req, backend, mat):
         yield req.forbidden('Unsupported service %s' % service)
         return
     req.nocache()
-    req.respond(HTTP_OK, 'application/x-%s-response' % service)
+    write = req.respond(HTTP_OK, 'application/x-%s-response' % service)
 
-    output = StringIO()
     input = req.environ['wsgi.input']
     # This is not necessary if this app is run from a conforming WSGI server.
     # Unfortunately, there's no way to tell that at this point.
@@ -241,10 +238,9 @@ def handle_service_request(req, backend, mat):
     content_length = req.environ.get('CONTENT_LENGTH', '')
     if content_length:
         input = _LengthLimitedFile(input, int(content_length))
-    proto = ReceivableProtocol(input.read, output.write)
+    proto = ReceivableProtocol(input.read, write)
     handler = handler_cls(backend, [url_prefix(mat)], proto, stateless_rpc=True)
     handler.handle()
-    yield output.getvalue()
 
 
 class HTTPGitRequest(object):
@@ -273,7 +269,7 @@ class HTTPGitRequest(object):
             self._headers.append(('Content-Type', content_type))
         self._headers.extend(self._cache_headers)
 
-        self._start_response(status, self._headers)
+        return self._start_response(status, self._headers)
 
     def not_found(self, message):
         """Begin a HTTP 404 response and return the text of a message."""