[Twisted-web] HTTP client TODOs

Scott Lamb slamb at slamb.org
Fri Nov 4 08:58:51 MST 2005

Jan Alonzo wrote:
> I am new to twisted and one of my priority right now is to help out with the
> HTTP client code in the web2 module. Is there a TODO specific to the client
> library somewhere that I can look at? 

I saw some '@@@' markers in comments indicating things that need to be 
fixed or tested.

I have some patches which aren't quite ready for inclusion. No unit 
tests, plus the stream-kludgey-reset.patch is true to its name; it needs 
to be reworked in the manner described in the list discussion.

Actually, the auth-python23.patch is ready, if a committer can merge it. 
I added a unit test in the last patch, which Jp broke on Python 2.3 when 
he applied it. (Don't mess with perfection. ;)

-------------- next part --------------
Fixes bug in which authorization retries of POST actions would not send the
actual data.


Index: twisted/web2/stream.py
--- twisted/web2/stream.py	(revision 14571)
+++ twisted/web2/stream.py	(working copy)
@@ -9,7 +9,8 @@
 The IStream interface is very simple. It consists of two methods:
 read, and close. The read method should either return some data, None
-if there is no data left to read, or a Deferred. Close frees up any
+if there is no data left to read, or a Deferred. The reset method will
+make subsequent reads start over from the beginning. Close frees up any
 underlying resources and causes read to return None forevermore.
 IByteStream adds a bit more to the API:
@@ -61,6 +62,10 @@
         Errors may be indicated by exception or by a Deferred of a Failure.
+    def reset():
+        """Resets the stream."""
     def close():
         """Prematurely close. Should also cause further reads to
         return None."""
@@ -249,16 +254,19 @@
             if len(mem) < length:
                 raise ValueError("len(mem) < start + length")
             self.length = length
+        self.written = False
+    def reset(self):
+        self.written = False
     def read(self):
-        if self.mem is None:
+        if self.mem is None or self.written:
             return None
+        self.written = True
         if self.length == 0:
             result = None
             result = buffer(self.mem, self.start, self.length)
-        self.mem = None
-        self.length = 0
         return result
     def close(self):
Index: twisted/web2/client/http.py
--- twisted/web2/client/http.py	(revision 14571)
+++ twisted/web2/client/http.py	(working copy)
@@ -52,8 +52,8 @@
     @ivar extraHeaders: Additional headers you want sent as part of the
         http request
-    @type body: str
-    @ivar body: The data associated with this request. This value can be None.
+    @type stream: IByteStream
+    @ivar stream: The data associated with this request. This value can be None.
     @type deferred: Deferred
     @ivar deferred: The C{Deferred} that will fire when this
@@ -286,7 +286,7 @@
         # Lastly, if there's a body, send that.
         if self.request.stream:
             if self.factory.logging:
-                _doLog("[Sent] body (%d byte(s))" % len(self.request.body))
+                _doLog("[Started Sending Body]")
             d = stream.StreamProducer(self.request.stream).beginProducing(self.transport)
@@ -709,6 +709,8 @@
         if authHeader and self.authHandlers:
             creds = self.authHandlers[authHeader[0].lower()].getCredentials(authHeader[1], request)
             if creds:
+                if request.stream is not None:
+                    request.stream.reset()
                 req = Request(request.method, request.uri, request.args,
                               request.headers, request.stream)
-------------- next part --------------
Fixes direct SSL connections. (Though you should use stunnel as a proxy,
anyway - it's much faster.)

Index: twisted/web2/client/http.py
--- twisted/web2/client/http.py	(revision 14553)
+++ twisted/web2/client/http.py	(working copy)
@@ -659,7 +659,7 @@
         if hasattr(self, "wrappingFactory"):
             result = reactor.connectTCP(self.host, self.port, self.wrappingFactory)
         elif self.sslContextFactory is not None:
-            result = reactor.connectSSL(self.host, self.port, self.sslContextFactory, self)
+            result = reactor.connectSSL(self.host, self.port, self, self.sslContextFactory)
             result = reactor.connectTCP(self.host, self.port, self)
-------------- next part --------------
Fixes a bug which caused a new HTTP connection for every request.

HTTPClient._endResponse() was calling self.response.stream.finish(), then
immediately checking if there are any more requests and, since there weren't
any, shutting down the connection. stream._StreamReader doesn't call the
completion deferred until later (with a callLater(0).) Thus, my load tester's
_handleFullResponse was never getting a chance to say "hey, I do have another
request". Give it that chance by using the same CallLater(0) trick before
deciding whether to tear down the connection.

Index: twisted/web2/client/http.py
--- twisted/web2/client/http.py	(revision 14553)
+++ twisted/web2/client/http.py	(working copy)
@@ -482,12 +482,18 @@
             self.state = CONNECTED
         self._decoder = None
-        connection = None
         if self.response is not None:
             if self.response.stream is not None:
+        reactor.callLater(0, self._endResponse2)
+    def _endResponse2(self):
+        connection = None
+        if self.factory.logging:
+            _doLog("[End Response 2]")
         # assert(self.connection is not None)
 #             if self.request is not None and self.request.deferred is not None:
 #                 self.request.deferred.callback(self.response)
-------------- next part --------------
Makes basic authorization work on Python 2.3, which doesn't have b64encode.

Index: twisted/web2/client/auth.py
--- twisted/web2/client/auth.py	(revision 14553)
+++ twisted/web2/client/auth.py	(working copy)
@@ -54,7 +54,7 @@
     scheme = 'Basic'
     def encodeCredentials(self, creds, challenge, request=None):
-        return base64.b64encode(':'.join(creds))
+        return base64.encodestring(':'.join(creds)).strip()
 class HTTPDigestAuthHandler(BaseHTTPAuthHandler):

More information about the Twisted-web mailing list