Merge lp:~djfroofy/txaws/modernize-924459 into lp:txaws

Proposed by Drew Smathers
Status: Merged
Approved by: Duncan McGreggor
Approved revision: 146
Merged at revision: 145
Proposed branch: lp:~djfroofy/txaws/modernize-924459
Merge into: lp:txaws
Diff against target: 670 lines (+459/-28)
5 files modified
txaws/client/_producers.py (+122/-0)
txaws/client/base.py (+178/-13)
txaws/client/tests/test_base.py (+116/-11)
txaws/client/tests/test_ssl.py (+20/-4)
txaws/testing/producers.py (+23/-0)
To merge this branch: bzr merge lp:~djfroofy/txaws/modernize-924459
Reviewer Review Type Date Requested Status
Duncan McGreggor Approve
Review via email: mp+92404@code.launchpad.net

Description of the change

Need to care how much we care about compatibility since: (1) This requires Twisted >= 11.1.0 so far as I know and (2) Some public members on BaseQuery no longer exist since they are not applicable when using Agent.

To post a comment you must log in.
lp:~djfroofy/txaws/modernize-924459 updated
145. By Drew Smathers

cherry pick: change StringIOReceiver to generic StreamingBodyReceiver

146. By Drew Smathers

check for response code in _handle_response and errback if >= 400 with response body

Revision history for this message
Duncan McGreggor (oubiwann) wrote :

On Thu, Feb 9, 2012 at 10:23 PM, Drew Smathers <email address hidden> wrote:
> Drew Smathers has proposed merging lp:~djfroofy/txaws/modernize-924459 into lp:txaws.
>
> Requested reviews:
>  Duncan McGreggor (oubiwann)
> Related bugs:
>  Bug #924459 in txAWS: "Modernize txaws.client to use twisted.web.client.Agent"
>  https://bugs.launchpad.net/txaws/+bug/924459
>
> For more details, see:
> https://code.launchpad.net/~djfroofy/txaws/modernize-924459/+merge/92404
>
> Need to care how much we care about compatibility since: (1) This requires Twisted >= 11.1.0 so far as I know and (2) Some public members on BaseQuery no longer exist since they are not applicable when using Agent.

I've taken a quick look at the latest code tonight, and first glance
is a happy one :-)

I've branched the code on my laptop so that I can review it on the
plane tomorrow, run the unit tests, etc.

I hope to have more for you soon.

Revision history for this message
Duncan McGreggor (oubiwann) wrote :

Merge away!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'txaws/client/_producers.py'
--- txaws/client/_producers.py 1970-01-01 00:00:00 +0000
+++ txaws/client/_producers.py 2012-03-15 05:30:25 +0000
@@ -0,0 +1,122 @@
1import os
2
3from zope.interface import implements
4
5from twisted.internet import defer, task
6from twisted.web.iweb import UNKNOWN_LENGTH, IBodyProducer
7
8
9# Code below for FileBodyProducer cut-and-paste from twisted source.
10# Currently this is not released so here temporarily for forward compat.
11
12
13class FileBodyProducer(object):
14 """
15 L{FileBodyProducer} produces bytes from an input file object incrementally
16 and writes them to a consumer.
17
18 Since file-like objects cannot be read from in an event-driven manner,
19 L{FileBodyProducer} uses a L{Cooperator} instance to schedule reads from
20 the file. This process is also paused and resumed based on notifications
21 from the L{IConsumer} provider being written to.
22
23 The file is closed after it has been read, or if the producer is stopped
24 early.
25
26 @ivar _inputFile: Any file-like object, bytes read from which will be
27 written to a consumer.
28
29 @ivar _cooperate: A method like L{Cooperator.cooperate} which is used to
30 schedule all reads.
31
32 @ivar _readSize: The number of bytes to read from C{_inputFile} at a time.
33 """
34 implements(IBodyProducer)
35
36 # Python 2.4 doesn't have these symbolic constants
37 _SEEK_SET = getattr(os, 'SEEK_SET', 0)
38 _SEEK_END = getattr(os, 'SEEK_END', 2)
39
40 def __init__(self, inputFile, cooperator=task, readSize=2 ** 16):
41 self._inputFile = inputFile
42 self._cooperate = cooperator.cooperate
43 self._readSize = readSize
44 self.length = self._determineLength(inputFile)
45
46
47 def _determineLength(self, fObj):
48 """
49 Determine how many bytes can be read out of C{fObj} (assuming it is not
50 modified from this point on). If the determination cannot be made,
51 return C{UNKNOWN_LENGTH}.
52 """
53 try:
54 seek = fObj.seek
55 tell = fObj.tell
56 except AttributeError:
57 return UNKNOWN_LENGTH
58 originalPosition = tell()
59 seek(0, self._SEEK_END)
60 end = tell()
61 seek(originalPosition, self._SEEK_SET)
62 return end - originalPosition
63
64
65 def stopProducing(self):
66 """
67 Permanently stop writing bytes from the file to the consumer by
68 stopping the underlying L{CooperativeTask}.
69 """
70 self._inputFile.close()
71 self._task.stop()
72
73
74 def startProducing(self, consumer):
75 """
76 Start a cooperative task which will read bytes from the input file and
77 write them to C{consumer}. Return a L{Deferred} which fires after all
78 bytes have been written.
79
80 @param consumer: Any L{IConsumer} provider
81 """
82 self._task = self._cooperate(self._writeloop(consumer))
83 d = self._task.whenDone()
84 def maybeStopped(reason):
85 # IBodyProducer.startProducing's Deferred isn't support to fire if
86 # stopProducing is called.
87 reason.trap(task.TaskStopped)
88 return defer.Deferred()
89 d.addCallbacks(lambda ignored: None, maybeStopped)
90 return d
91
92
93 def _writeloop(self, consumer):
94 """
95 Return an iterator which reads one chunk of bytes from the input file
96 and writes them to the consumer for each time it is iterated.
97 """
98 while True:
99 bytes = self._inputFile.read(self._readSize)
100 if not bytes:
101 self._inputFile.close()
102 break
103 consumer.write(bytes)
104 yield None
105
106
107 def pauseProducing(self):
108 """
109 Temporarily suspend copying bytes from the input file to the consumer
110 by pausing the L{CooperativeTask} which drives that activity.
111 """
112 self._task.pause()
113
114
115 def resumeProducing(self):
116 """
117 Undo the effects of a previous C{pauseProducing} and resume copying
118 bytes to the consumer by resuming the L{CooperativeTask} which drives
119 the write activity.
120 """
121 self._task.resume()
122
0123
=== modified file 'txaws/client/base.py'
--- txaws/client/base.py 2011-11-29 08:17:54 +0000
+++ txaws/client/base.py 2012-03-15 05:30:25 +0000
@@ -3,10 +3,25 @@
3except ImportError:3except ImportError:
4 from xml.parsers.expat import ExpatError as ParseError4 from xml.parsers.expat import ExpatError as ParseError
55
6import warnings
7from StringIO import StringIO
8
6from twisted.internet.ssl import ClientContextFactory9from twisted.internet.ssl import ClientContextFactory
10from twisted.internet.protocol import Protocol
11from twisted.internet.defer import Deferred, succeed, fail
12from twisted.python import failure
7from twisted.web import http13from twisted.web import http
14from twisted.web.iweb import UNKNOWN_LENGTH
8from twisted.web.client import HTTPClientFactory15from twisted.web.client import HTTPClientFactory
16from twisted.web.client import Agent
17from twisted.web.client import ResponseDone
18from twisted.web.http import NO_CONTENT
19from twisted.web.http_headers import Headers
9from twisted.web.error import Error as TwistedWebError20from twisted.web.error import Error as TwistedWebError
21try:
22 from twisted.web.client import FileBodyProducer
23except ImportError:
24 from txaws.client._producers import FileBodyProducer
1025
11from txaws.util import parse26from txaws.util import parse
12from txaws.credentials import AWSCredentials27from txaws.credentials import AWSCredentials
@@ -71,20 +86,122 @@
71 self.query_factory = query_factory86 self.query_factory = query_factory
72 self.parser = parser87 self.parser = parser
7388
89class StreamingError(Exception):
90 """
91 Raised if more data or less data is received than expected.
92 """
93
94
95class StreamingBodyReceiver(Protocol):
96 """
97 Streaming HTTP response body receiver.
98
99 TODO: perhaps there should be an interface specifying why
100 finished (Deferred) and content_length are necessary and
101 how to used them; eg. callback/errback finished on completion.
102 """
103 finished = None
104 content_length = None
105
106 def __init__(self, fd=None, readback=True):
107 """
108 @param fd: a file descriptor to write to
109 @param readback: if True read back data from fd to callback finished
110 with, otherwise we call back finish with fd itself
111 with
112 """
113 if fd is None:
114 fd = StringIO()
115 self._fd = fd
116 self._received = 0
117 self._readback = readback
118
119 def dataReceived(self, bytes):
120 streaming = self.content_length is UNKNOWN_LENGTH
121 if not streaming and (self._received > self.content_length):
122 self.transport.loseConnection()
123 raise StreamingError(
124 "Buffer overflow - received more data than "
125 "Content-Length dictated: %d" % self.content_length)
126 # TODO should be some limit on how much we receive
127 self._fd.write(bytes)
128 self._received += len(bytes)
129
130 def connectionLost(self, reason):
131 reason.trap(ResponseDone)
132 d = self.finished
133 self.finished = None
134 streaming = self.content_length is UNKNOWN_LENGTH
135 if streaming or (self._received == self.content_length):
136 if self._readback:
137 self._fd.seek(0)
138 data = self._fd.read()
139 self._fd.close()
140 self._fd = None
141 d.callback(data)
142 else:
143 d.callback(self._fd)
144 else:
145 f = failure.Failure(StreamingError("Connection lost before "
146 "receiving all data"))
147 d.errback(f)
148
149
150class WebClientContextFactory(ClientContextFactory):
151
152 def getContext(self, hostname, port):
153 return ClientContextFactory.getContext(self)
154
155
156class WebVerifyingContextFactory(VerifyingContextFactory):
157
158 def getContext(self, hostname, port):
159 return VerifyingContextFactory.getContext(self)
160
161
162class FakeClient(object):
163 """
164 XXX
165 A fake client object for some degree of backwards compatability for
166 code using the client attibute on BaseQuery to check url, status
167 etc.
168 """
169 url = None
170 status = None
74171
75class BaseQuery(object):172class BaseQuery(object):
76173
77 def __init__(self, action=None, creds=None, endpoint=None, reactor=None):174 def __init__(self, action=None, creds=None, endpoint=None, reactor=None,
175 body_producer=None, receiver_factory=None):
78 if not action:176 if not action:
79 raise TypeError("The query requires an action parameter.")177 raise TypeError("The query requires an action parameter.")
80 self.factory = HTTPClientFactory
81 self.action = action178 self.action = action
82 self.creds = creds179 self.creds = creds
83 self.endpoint = endpoint180 self.endpoint = endpoint
84 if reactor is None:181 if reactor is None:
85 from twisted.internet import reactor182 from twisted.internet import reactor
86 self.reactor = reactor183 self.reactor = reactor
87 self.client = None184 self._client = None
185 self.request_headers = None
186 self.response_headers = None
187 self.body_producer = body_producer
188 self.receiver_factory = receiver_factory or StreamingBodyReceiver
189
190 @property
191 def client(self):
192 if self._client is None:
193 self._client_deprecation_warning()
194 self._client = FakeClient()
195 return self._client
196
197 @client.setter
198 def client(self, value):
199 self._client_deprecation_warning()
200 self._client = value
201
202 def _client_deprecation_warning(self):
203 warnings.warn('The client attribute on BaseQuery is deprecated and'
204 ' will go away in future release.')
88205
89 def get_page(self, url, *args, **kwds):206 def get_page(self, url, *args, **kwds):
90 """207 """
@@ -95,16 +212,39 @@
95 """212 """
96 contextFactory = None213 contextFactory = None
97 scheme, host, port, path = parse(url)214 scheme, host, port, path = parse(url)
98 self.client = self.factory(url, *args, **kwds)215 data = kwds.get('postdata', None)
216 self._method = method = kwds.get('method', 'GET')
217 self.request_headers = self._headers(kwds.get('headers', {}))
218 if (self.body_producer is None) and (data is not None):
219 self.body_producer = FileBodyProducer(StringIO(data))
99 if scheme == "https":220 if scheme == "https":
100 if self.endpoint.ssl_hostname_verification:221 if self.endpoint.ssl_hostname_verification:
101 contextFactory = VerifyingContextFactory(host)222 contextFactory = WebVerifyingContextFactory(host)
102 else:223 else:
103 contextFactory = ClientContextFactory()224 contextFactory = WebClientContextFactory()
104 self.reactor.connectSSL(host, port, self.client, contextFactory)225 agent = Agent(self.reactor, contextFactory)
226 self.client.url = url
227 d = agent.request(method, url, self.request_headers,
228 self.body_producer)
105 else:229 else:
106 self.reactor.connectTCP(host, port, self.client)230 agent = Agent(self.reactor)
107 return self.client.deferred231 d = agent.request(method, url, self.request_headers,
232 self.body_producer)
233 d.addCallback(self._handle_response)
234 return d
235
236 def _headers(self, headers_dict):
237 """
238 Convert dictionary of headers into twisted.web.client.Headers object.
239 """
240 return Headers(dict((k,[v]) for (k,v) in headers_dict.items()))
241
242 def _unpack_headers(self, headers):
243 """
244 Unpack twisted.web.client.Headers object to dict. This is to provide
245 backwards compatability.
246 """
247 return dict((k,v[0]) for (k,v) in headers.getAllRawHeaders())
108248
109 def get_request_headers(self, *args, **kwds):249 def get_request_headers(self, *args, **kwds):
110 """250 """
@@ -114,8 +254,32 @@
114 The AWS S3 API depends upon setting headers. This method is provided as254 The AWS S3 API depends upon setting headers. This method is provided as
115 a convenience for debugging issues with the S3 communications.255 a convenience for debugging issues with the S3 communications.
116 """256 """
117 if self.client:257 if self.request_headers:
118 return self.client.headers258 return self._unpack_headers(self.request_headers)
259
260 def _handle_response(self, response):
261 """
262 Handle the HTTP response by memoing the headers and then delivering
263 bytes.
264 """
265 self.client.status = response.code
266 self.response_headers = headers = response.headers
267 # XXX This workaround (which needs to be improved at that) for possible
268 # bug in Twisted with new client:
269 # http://twistedmatrix.com/trac/ticket/5476
270 if self._method.upper() == 'HEAD' or response.code == NO_CONTENT:
271 return succeed('')
272 receiver = self.receiver_factory()
273 receiver.finished = d = Deferred()
274 receiver.content_length = response.length
275 response.deliverBody(receiver)
276 if response.code >= 400:
277 d.addCallback(self._fail_response, response)
278 return d
279
280 def _fail_response(self, data, response):
281 return fail(failure.Failure(
282 TwistedWebError(response.code, response=data)))
119283
120 def get_response_headers(self, *args, **kwargs):284 def get_response_headers(self, *args, **kwargs):
121 """285 """
@@ -125,5 +289,6 @@
125 The AWS S3 API depends upon setting headers. This method is used by the289 The AWS S3 API depends upon setting headers. This method is used by the
126 head_object API call for getting a S3 object's metadata.290 head_object API call for getting a S3 object's metadata.
127 """291 """
128 if self.client:292 if self.response_headers:
129 return self.client.response_headers293 return self._unpack_headers(self.response_headers)
294
130295
=== modified file 'txaws/client/tests/test_base.py'
--- txaws/client/tests/test_base.py 2012-01-26 18:43:48 +0000
+++ txaws/client/tests/test_base.py 2012-03-15 05:30:25 +0000
@@ -1,6 +1,11 @@
1import os1import os
22
3from StringIO import StringIO
4
5from zope.interface import implements
6
3from twisted.internet import reactor7from twisted.internet import reactor
8from twisted.internet.defer import succeed, Deferred
4from twisted.internet.error import ConnectionRefusedError9from twisted.internet.error import ConnectionRefusedError
5from twisted.protocols.policies import WrappingFactory10from twisted.protocols.policies import WrappingFactory
6from twisted.python import log11from twisted.python import log
@@ -8,14 +13,18 @@
8from twisted.python.failure import Failure13from twisted.python.failure import Failure
9from twisted.test.test_sslverify import makeCertificate14from twisted.test.test_sslverify import makeCertificate
10from twisted.web import server, static15from twisted.web import server, static
16from twisted.web.iweb import IBodyProducer
11from twisted.web.client import HTTPClientFactory17from twisted.web.client import HTTPClientFactory
18from twisted.web.client import ResponseDone
19from twisted.web.resource import Resource
12from twisted.web.error import Error as TwistedWebError20from twisted.web.error import Error as TwistedWebError
1321
14from txaws.client import ssl22from txaws.client import ssl
15from txaws.client.base import BaseClient, BaseQuery, error_wrapper23from txaws.client.base import BaseClient, BaseQuery, error_wrapper
24from txaws.client.base import StreamingBodyReceiver
16from txaws.service import AWSServiceEndpoint25from txaws.service import AWSServiceEndpoint
17from txaws.testing.base import TXAWSTestCase26from txaws.testing.base import TXAWSTestCase
1827from txaws.testing.producers import StringBodyProducer
1928
20class ErrorWrapperTestCase(TXAWSTestCase):29class ErrorWrapperTestCase(TXAWSTestCase):
2130
@@ -63,6 +72,12 @@
63 self.assertEquals(client.parser, "parser")72 self.assertEquals(client.parser, "parser")
6473
6574
75class PuttableResource(Resource):
76
77 def render_PUT(self, reuqest):
78 return ''
79
80
66class BaseQueryTestCase(TXAWSTestCase):81class BaseQueryTestCase(TXAWSTestCase):
6782
68 def setUp(self):83 def setUp(self):
@@ -71,6 +86,7 @@
71 os.mkdir(name)86 os.mkdir(name)
72 FilePath(name).child("file").setContent("0123456789")87 FilePath(name).child("file").setContent("0123456789")
73 r = static.File(name)88 r = static.File(name)
89 r.putChild('thing_to_put', PuttableResource())
74 self.site = server.Site(r, timeout=None)90 self.site = server.Site(r, timeout=None)
75 self.wrapper = WrappingFactory(self.site)91 self.wrapper = WrappingFactory(self.site)
76 self.port = self._listen(self.wrapper)92 self.port = self._listen(self.wrapper)
@@ -99,7 +115,6 @@
99115
100 def test_creation(self):116 def test_creation(self):
101 query = BaseQuery("an action", "creds", "http://endpoint")117 query = BaseQuery("an action", "creds", "http://endpoint")
102 self.assertEquals(query.factory, HTTPClientFactory)
103 self.assertEquals(query.action, "an action")118 self.assertEquals(query.action, "an action")
104 self.assertEquals(query.creds, "creds")119 self.assertEquals(query.creds, "creds")
105 self.assertEquals(query.endpoint, "http://endpoint")120 self.assertEquals(query.endpoint, "http://endpoint")
@@ -142,16 +157,58 @@
142 def test_get_response_headers_with_client(self):157 def test_get_response_headers_with_client(self):
143158
144 def check_results(results):159 def check_results(results):
160 #self.assertEquals(sorted(results.keys()), [
161 # "accept-ranges", "content-length", "content-type", "date",
162 # "last-modified", "server"])
163 # XXX I think newclient exludes content-length from headers?
164 # Also the header names are capitalized ... do we need to worry
165 # about backwards compat?
145 self.assertEquals(sorted(results.keys()), [166 self.assertEquals(sorted(results.keys()), [
146 "accept-ranges", "content-length", "content-type", "date",167 "Accept-Ranges", "Content-Type", "Date",
147 "last-modified", "server"])168 "Last-Modified", "Server"])
148 self.assertEquals(len(results.values()), 6)169 self.assertEquals(len(results.values()), 5)
149170
150 query = BaseQuery("an action", "creds", "http://endpoint")171 query = BaseQuery("an action", "creds", "http://endpoint")
151 d = query.get_page(self._get_url("file"))172 d = query.get_page(self._get_url("file"))
152 d.addCallback(query.get_response_headers)173 d.addCallback(query.get_response_headers)
153 return d.addCallback(check_results)174 return d.addCallback(check_results)
154175
176 def test_errors(self):
177 query = BaseQuery("an action", "creds", "http://endpoint")
178 d = query.get_page(self._get_url("not_there"))
179 self.assertFailure(d, TwistedWebError)
180 return d
181
182 def test_custom_body_producer(self):
183
184 def check_producer_was_used(ignore):
185 self.assertEqual(producer.written, 'test data')
186
187 producer = StringBodyProducer('test data')
188 query = BaseQuery("an action", "creds", "http://endpoint",
189 body_producer=producer)
190 d = query.get_page(self._get_url("thing_to_put"), method='PUT')
191 return d.addCallback(check_producer_was_used)
192
193 def test_custom_receiver_factory(self):
194
195 class TestReceiverProtocol(StreamingBodyReceiver):
196 used = False
197
198 def __init__(self):
199 StreamingBodyReceiver.__init__(self)
200 TestReceiverProtocol.used = True
201
202 def check_used(ignore):
203 self.assert_(TestReceiverProtocol.used)
204
205 query = BaseQuery("an action", "creds", "http://endpoint",
206 receiver_factory=TestReceiverProtocol)
207 d = query.get_page(self._get_url("file"))
208 d.addCallback(self.assertEquals, "0123456789")
209 d.addCallback(check_used)
210 return d
211
155 # XXX for systems that don't have certs in the DEFAULT_CERT_PATH, this test212 # XXX for systems that don't have certs in the DEFAULT_CERT_PATH, this test
156 # will fail; instead, let's create some certs in a temp directory and set213 # will fail; instead, let's create some certs in a temp directory and set
157 # the DEFAULT_CERT_PATH to point there.214 # the DEFAULT_CERT_PATH to point there.
@@ -167,8 +224,9 @@
167 def __init__(self):224 def __init__(self):
168 self.connects = []225 self.connects = []
169226
170 def connectSSL(self, host, port, client, factory):227 def connectSSL(self, host, port, factory, contextFactory, timeout,
171 self.connects.append((host, port, client, factory))228 bindAddress):
229 self.connects.append((host, port, factory, contextFactory))
172230
173 certs = makeCertificate(O="Test Certificate", CN="something")[1]231 certs = makeCertificate(O="Test Certificate", CN="something")[1]
174 self.patch(ssl, "_ca_certs", certs)232 self.patch(ssl, "_ca_certs", certs)
@@ -176,9 +234,56 @@
176 endpoint = AWSServiceEndpoint(ssl_hostname_verification=True)234 endpoint = AWSServiceEndpoint(ssl_hostname_verification=True)
177 query = BaseQuery("an action", "creds", endpoint, fake_reactor)235 query = BaseQuery("an action", "creds", endpoint, fake_reactor)
178 query.get_page("https://example.com/file")236 query.get_page("https://example.com/file")
179 [(host, port, client, factory)] = fake_reactor.connects237 [(host, port, factory, contextFactory)] = fake_reactor.connects
180 self.assertEqual("example.com", host)238 self.assertEqual("example.com", host)
181 self.assertEqual(443, port)239 self.assertEqual(443, port)
182 self.assertTrue(isinstance(factory, ssl.VerifyingContextFactory))240 wrappedFactory = contextFactory._webContext
183 self.assertEqual("example.com", factory.host)241 self.assertTrue(isinstance(wrappedFactory, ssl.VerifyingContextFactory))
184 self.assertNotEqual([], factory.caCerts)242 self.assertEqual("example.com", wrappedFactory.host)
243 self.assertNotEqual([], wrappedFactory.caCerts)
244
245class StreamingBodyReceiverTestCase(TXAWSTestCase):
246
247 def test_readback_mode_on(self):
248 """
249 Test that when readback mode is on inside connectionLost() data will
250 be read back from the start of the file we're streaming and results
251 passed to finished callback.
252 """
253
254 receiver = StreamingBodyReceiver()
255 d = Deferred()
256 receiver.finished = d
257 receiver.content_length = 5
258 fd = receiver._fd
259 receiver.dataReceived('hello')
260 why = Failure(ResponseDone('done'))
261 receiver.connectionLost(why)
262 self.assertEqual(d.result, 'hello')
263 self.assert_(fd.closed)
264
265 def test_readback_mode_off(self):
266 """
267 Test that when readback mode is off connectionLost() will simply
268 callback finished with the fd.
269 """
270
271 receiver = StreamingBodyReceiver(readback=False)
272 d = Deferred()
273 receiver.finished = d
274 receiver.content_length = 5
275 fd = receiver._fd
276 receiver.dataReceived('hello')
277 why = Failure(ResponseDone('done'))
278 receiver.connectionLost(why)
279 self.assertIdentical(d.result, fd)
280 self.assertIdentical(receiver._fd, fd)
281 self.failIf(fd.closed)
282
283 def test_user_fd(self):
284 """
285 Test that user's own file descriptor can be passed to init
286 """
287 user_fd = StringIO()
288 receiver = StreamingBodyReceiver(user_fd)
289 self.assertIdentical(receiver._fd, user_fd)
185290
=== modified file 'txaws/client/tests/test_ssl.py'
--- txaws/client/tests/test_ssl.py 2012-01-26 22:54:44 +0000
+++ txaws/client/tests/test_ssl.py 2012-03-15 05:30:25 +0000
@@ -12,6 +12,10 @@
12from twisted.python.filepath import FilePath12from twisted.python.filepath import FilePath
13from twisted.test.test_sslverify import makeCertificate13from twisted.test.test_sslverify import makeCertificate
14from twisted.web import server, static14from twisted.web import server, static
15try:
16 from twisted.web.client import ResponseFailed
17except ImportError:
18 from twisted.web._newclient import ResponseFailed
1519
16from txaws import exception20from txaws import exception
17from txaws.client import ssl21from txaws.client import ssl
@@ -32,6 +36,11 @@
32PUBSANKEY = sibpath("public_san.ssl")36PUBSANKEY = sibpath("public_san.ssl")
3337
3438
39class WebDefaultOpenSSLContextFactory(DefaultOpenSSLContextFactory):
40 def getContext(self, hostname=None, port=None):
41 return DefaultOpenSSLContextFactory.getContext(self)
42
43
35class BaseQuerySSLTestCase(TXAWSTestCase):44class BaseQuerySSLTestCase(TXAWSTestCase):
3645
37 def setUp(self):46 def setUp(self):
@@ -75,7 +84,7 @@
75 The L{VerifyingContextFactory} properly allows to connect to the84 The L{VerifyingContextFactory} properly allows to connect to the
76 endpoint if the certificates match.85 endpoint if the certificates match.
77 """86 """
78 context_factory = DefaultOpenSSLContextFactory(PRIVKEY, PUBKEY)87 context_factory = WebDefaultOpenSSLContextFactory(PRIVKEY, PUBKEY)
79 self.port = reactor.listenSSL(88 self.port = reactor.listenSSL(
80 0, self.site, context_factory, interface="127.0.0.1")89 0, self.site, context_factory, interface="127.0.0.1")
81 self.portno = self.port.getHost().port90 self.portno = self.port.getHost().port
@@ -90,7 +99,7 @@
90 The L{VerifyingContextFactory} fails with a SSL error the certificates99 The L{VerifyingContextFactory} fails with a SSL error the certificates
91 can't be checked.100 can't be checked.
92 """101 """
93 context_factory = DefaultOpenSSLContextFactory(BADPRIVKEY, BADPUBKEY)102 context_factory = WebDefaultOpenSSLContextFactory(BADPRIVKEY, BADPUBKEY)
94 self.port = reactor.listenSSL(103 self.port = reactor.listenSSL(
95 0, self.site, context_factory, interface="127.0.0.1")104 0, self.site, context_factory, interface="127.0.0.1")
96 self.portno = self.port.getHost().port105 self.portno = self.port.getHost().port
@@ -98,7 +107,14 @@
98 endpoint = AWSServiceEndpoint(ssl_hostname_verification=True)107 endpoint = AWSServiceEndpoint(ssl_hostname_verification=True)
99 query = BaseQuery("an action", "creds", endpoint)108 query = BaseQuery("an action", "creds", endpoint)
100 d = query.get_page(self._get_url("file"))109 d = query.get_page(self._get_url("file"))
101 return self.assertFailure(d, SSLError)110 def fail(ignore):
111 self.fail('Expected SSLError')
112 def check_exception(why):
113 # XXX kind of a mess here ... need to unwrap the
114 # exception and check
115 root_exc = why.value[0][0].value
116 self.assert_(isinstance(root_exc, SSLError))
117 return d.addCallbacks(fail, check_exception)
102118
103 def test_ssl_verification_bypassed(self):119 def test_ssl_verification_bypassed(self):
104 """120 """
@@ -121,7 +137,7 @@
121 L{VerifyingContextFactory} supports checking C{subjectAltName} in the137 L{VerifyingContextFactory} supports checking C{subjectAltName} in the
122 certificate if it's available.138 certificate if it's available.
123 """139 """
124 context_factory = DefaultOpenSSLContextFactory(PRIVSANKEY, PUBSANKEY)140 context_factory = WebDefaultOpenSSLContextFactory(PRIVSANKEY, PUBSANKEY)
125 self.port = reactor.listenSSL(141 self.port = reactor.listenSSL(
126 0, self.site, context_factory, interface="127.0.0.1")142 0, self.site, context_factory, interface="127.0.0.1")
127 self.portno = self.port.getHost().port143 self.portno = self.port.getHost().port
128144
=== added file 'txaws/testing/producers.py'
--- txaws/testing/producers.py 1970-01-01 00:00:00 +0000
+++ txaws/testing/producers.py 2012-03-15 05:30:25 +0000
@@ -0,0 +1,23 @@
1from zope.interface import implements
2
3from twisted.internet.defer import succeed
4from twisted.web.iweb import IBodyProducer
5
6class StringBodyProducer(object):
7 implements(IBodyProducer)
8
9 def __init__(self, data):
10 self.data = data
11 self.length = len(data)
12 self.written = None
13
14 def startProducing(self, consumer):
15 consumer.write(self.data)
16 self.written = self.data
17 return succeed(None)
18
19 def pauseProducing(self):
20 pass
21
22 def stopProducing(self):
23 pass

Subscribers

People subscribed via source and target branches