Merge lp:~brian.curtin/ubuntuone-dev-tools/py3-getPage-replacement into lp:ubuntuone-dev-tools

Proposed by Brian Curtin
Status: Rejected
Rejected by: dobey
Proposed branch: lp:~brian.curtin/ubuntuone-dev-tools/py3-getPage-replacement
Merge into: lp:ubuntuone-dev-tools
Diff against target: 307 lines (+84/-110)
2 files modified
ubuntuone/devtools/testing/tests/test_txwebserver.py (+42/-38)
ubuntuone/devtools/testing/txwebserver.py (+42/-72)
To merge this branch: bzr merge lp:~brian.curtin/ubuntuone-dev-tools/py3-getPage-replacement
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Alejandro J. Cura (community) Needs Fixing
Review via email: mp+122768@code.launchpad.net

Commit message

- Move to a standard library based HTTPWebSever and HTTPSWebServer implementation due to lack of twisted support for those implementations on Python 3.

Description of the change

As twisted.web.client.getPage is not currently available for Python 3 (nor is it in scope for the current porting efforts), we need to replace it with a standard equivalent, which is available via urllib.

While moving towards that urllib implementation, it was pointed out that the server would have to be moved away from twisted as well, so HTTPWebServer and HTTPSWebServer were moved from twisted implementations to similar standard library equivalents using socketserver.ThreadedMixIn, socketserver.TCPServer, and the ssl module.

With the exception of HTTPSWebSever, everything here works on Python 2 and 3. HTTPS is currently untested within devtools, but *is* tested within all of the projects that use it. In the interest of progressing on SSO porting, I haven't added any HTTPS specific tests but intend to bring some back from SSO and the other projects as I encounter them (and fix them).

To post a comment you must log in.
Revision history for this message
Alejandro J. Cura (alecu) wrote :

When running the sso tests with this branch I get a lot of errors similar to this:

test_ssl_fail_dialog_user_accepts_via_proxy ... Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/twisted/internet/defer.py", line 1039, in _inlineCallbacks
    result = g.send(result)
  File "/home/alecu/canonical/ubuntu-sso-client/trunk/ubuntu_sso/utils/webclient/tests/test_webclient.py", line 969, in setUp
    self.ws = HTTPSMockWebServer(self.ssl_settings)
  File "/home/alecu/canonical/ubuntu-sso-client/trunk/ubuntu_sso/utils/webclient/tests/test_webclient.py", line 239, in __init__
    super(HTTPSMockWebServer, self).__init__(root, ssl_settings)
exceptions.TypeError: must be type, not classobj

review: Needs Fixing
102. By Brian Curtin

Shouldn't inherit from the mixin/TCPServer - instantiate them in the BaseWebSever and then use super all around. Should fix alecu's comment.

Revision history for this message
Roberto Alsina (ralsina) wrote :

Looks good to me.

review: Approve
Revision history for this message
Brian Curtin (brian.curtin) wrote :

Don't mark the branch as approved yet - it breaks SSO horribly so I'm working on making the changes for it to work with this branch.

Unmerged revisions

102. By Brian Curtin

Shouldn't inherit from the mixin/TCPServer - instantiate them in the BaseWebSever and then use super all around. Should fix alecu's comment.

101. By Brian Curtin

Remove commented out lines

100. By Brian Curtin

Restructure BaseWebSever to ease HTTPSWebServer implementation. Remove all Twisted code and imports

99. By Brian Curtin

Suppress HTTPServer output by overriding the log_message method. This removes the various output that gets mixed in with the test runner output.

98. By Brian Curtin

Merge trunk

97. By Brian Curtin

Various pylint fixes

96. By Brian Curtin

Initial implementation of stdlib HTTPServer

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/devtools/testing/tests/test_txwebserver.py'
2--- ubuntuone/devtools/testing/tests/test_txwebserver.py 2012-08-28 20:28:43 +0000
3+++ ubuntuone/devtools/testing/tests/test_txwebserver.py 2012-09-07 18:28:19 +0000
4@@ -30,9 +30,17 @@
5
6 from __future__ import unicode_literals
7
8+# pylint: disable=F0401,E0611
9+try:
10+ from http.server import SimpleHTTPRequestHandler
11+ from urllib.request import urlopen
12+except ImportError:
13+ from SimpleHTTPServer import SimpleHTTPRequestHandler
14+ from urllib import urlopen
15+# pylint: disable=F0401,E0611
16+
17 from twisted.internet import defer
18 from twisted.trial.unittest import TestCase
19-from twisted.web import client, resource
20
21 from ubuntuone.devtools.testing.txwebserver import HTTPWebServer
22
23@@ -50,15 +58,31 @@
24 # pylint: disable=W0232
25
26
27-class SimpleResource(resource.Resource):
28- """A simple web resource."""
29+# pylint: disable=C0103
30+def getPage(url):
31+ """Simple standard library replacement for twisted.web.client.getPage."""
32+ data = urlopen(url)
33+ text = data.read()
34+ return text
35+# pylint: enable=C0103
36+
37+
38+class RequestHandler(SimpleHTTPRequestHandler):
39+ """A HTTP request handler."""
40
41 # pylint: disable=C0103
42- def render_GET(self, request):
43- """Make a bit of html out of these resource's content."""
44- return SAMPLE_RESOURCE
45+ def do_GET(self):
46+ """Simply return SAMPLE_RESOURCE for all GET requests."""
47+ self.send_response(200)
48+ self.send_header("Content-type", "text/html")
49+ self.end_headers()
50+ self.wfile.write(SAMPLE_RESOURCE)
51 # pylint: enable=C0103
52
53+ def log_message(self, *args):
54+ """Override the default log message so we don't output anything."""
55+ return
56+
57
58 class WebServerTestCase(TestCase):
59 """Test the web server that will allow to have connections."""
60@@ -68,34 +92,24 @@
61 """Set the different tests."""
62 yield super(WebServerTestCase, self).setUp()
63 # create the root to be used by the webserver
64- root = resource.Resource()
65- root.putChild(SIMPLERESOURCE, SimpleResource())
66- root.putChild(OTHER_SIMPLERESOURCE, SimpleResource())
67- root.putChild(THROWERROR, resource.NoResource())
68-
69- unauthorized_resource = resource.ErrorPage(resource.http.UNAUTHORIZED,
70- "Unauthorized", "Unauthorized")
71- root.putChild(UNAUTHORIZED, unauthorized_resource)
72- self.server = HTTPWebServer(root)
73+ self.server = HTTPWebServer(RequestHandler)
74 self.server.start()
75 self.uri = "http://127.0.0.1:{port}/".format(
76 port=self.server.get_port()).encode("utf8")
77 self.addCleanup(self.server.stop)
78
79- @defer.inlineCallbacks
80 def test_single_request(self):
81 """Test performing a single request to get the data."""
82 url = self.uri + SIMPLERESOURCE
83- result = yield client.getPage(url)
84+ result = getPage(url.decode("utf8"))
85 self.assertEqual(SAMPLE_RESOURCE, result)
86
87- @defer.inlineCallbacks
88 def test_multiple_requests(self):
89 """Test performing multiple requests."""
90 simple_url = self.uri + SIMPLERESOURCE
91 other_simple_url = self.uri + OTHER_SIMPLERESOURCE
92- simple_result = yield client.getPage(simple_url)
93- other_result = yield client.getPage(other_simple_url)
94+ simple_result = getPage(simple_url.decode("utf8"))
95+ other_result = getPage(other_simple_url.decode("utf8"))
96 self.assertEqual(SAMPLE_RESOURCE, simple_result)
97 self.assertEqual(SAMPLE_RESOURCE, other_result)
98
99@@ -112,7 +126,7 @@
100 def test_get_port(self):
101 """Test getting the port."""
102 # pylint: disable=W0212
103- port = self.server.port.getHost().port
104+ port = self.server.port
105 # pylint: enable=W0212
106 self.assertEqual(port, self.server.get_port())
107
108@@ -124,51 +138,41 @@
109 def setUp(self):
110 """Set the diff tests."""
111 yield super(MultipleWebServersTestCase, self).setUp()
112- self.root = resource.Resource()
113- self.root.putChild(SIMPLERESOURCE, SimpleResource())
114- self.root.putChild(OTHER_SIMPLERESOURCE, SimpleResource())
115- self.root.putChild(THROWERROR, resource.NoResource())
116-
117- unauthorized_resource = resource.ErrorPage(resource.http.UNAUTHORIZED,
118- "Unauthorized", "Unauthorized")
119- self.root.putChild(UNAUTHORIZED, unauthorized_resource)
120
121 def get_uri(self, server):
122 """Return the uri for the server."""
123 url = "http://127.0.0.1:{port}/"
124 return url.format(port=server.get_port()).encode("utf8")
125
126- @defer.inlineCallbacks
127 def test_single_request(self):
128 """Test performing a single request to get the data."""
129- first_server = HTTPWebServer(self.root)
130+ first_server = HTTPWebServer(RequestHandler)
131 first_server.start()
132 self.addCleanup(first_server.stop)
133
134- second_server = HTTPWebServer(self.root)
135+ second_server = HTTPWebServer(RequestHandler)
136 second_server.start()
137 self.addCleanup(second_server.stop)
138
139 for server in [first_server, second_server]:
140 url = self.get_uri(server) + SIMPLERESOURCE
141- result = yield client.getPage(url)
142+ result = getPage(url.decode("utf8"))
143 self.assertEqual(SAMPLE_RESOURCE, result)
144
145- @defer.inlineCallbacks
146 def test_multiple_requests(self):
147 """Test performing multiple requests."""
148- first_server = HTTPWebServer(self.root)
149+ first_server = HTTPWebServer(RequestHandler)
150 first_server.start()
151 self.addCleanup(first_server.stop)
152
153- second_server = HTTPWebServer(self.root)
154+ second_server = HTTPWebServer(RequestHandler)
155 second_server.start()
156 self.addCleanup(second_server.stop)
157
158 for server in [first_server, second_server]:
159 simple_url = self.get_uri(server) + SIMPLERESOURCE
160 other_simple_url = self.get_uri(server) + OTHER_SIMPLERESOURCE
161- simple_result = yield client.getPage(simple_url)
162- other_result = yield client.getPage(other_simple_url)
163+ simple_result = getPage(simple_url.decode("utf8"))
164+ other_result = getPage(other_simple_url.decode("utf8"))
165 self.assertEqual(SAMPLE_RESOURCE, simple_result)
166 self.assertEqual(SAMPLE_RESOURCE, other_result)
167
168=== modified file 'ubuntuone/devtools/testing/txwebserver.py'
169--- ubuntuone/devtools/testing/txwebserver.py 2012-08-21 21:36:20 +0000
170+++ ubuntuone/devtools/testing/txwebserver.py 2012-09-07 18:28:19 +0000
171@@ -30,94 +30,64 @@
172
173 from __future__ import unicode_literals
174
175-from twisted.internet import defer, reactor, ssl
176-from twisted.protocols.policies import WrappingFactory
177-from twisted.web import server
178-
179-from ubuntuone.devtools.testcases.txsocketserver import server_protocol_factory
180-
181-# no init method + twisted common warnings
182-# pylint: disable=W0232, C0103, E1101
183+# pylint: disable=F0401
184+try:
185+ from socketserver import ThreadingMixIn, TCPServer
186+except ImportError:
187+ from SocketServer import ThreadingMixIn, TCPServer
188+# pylint: enable=F0401
189+
190+import ssl
191+import threading
192+
193+
194+class ThreadedWebServer(ThreadingMixIn, TCPServer):
195+ """Threaded web server."""
196
197
198 class BaseWebServer(object):
199- """Webserver used to perform requests in tests."""
200-
201- def __init__(self, root_resource, scheme):
202- """Create and start the instance.
203-
204- The ssl_settings parameter contains a dictionary with the key and cert
205- that will be used to perform ssl connections. The root_resource
206- contains the resource with all its childre.
207- """
208- self.root = root_resource
209+ """Web server"""
210+
211+ def __init__(self, handler, scheme):
212+ self.server = ThreadedWebServer(("localhost", 0), handler)
213+ self.ip, self.port = self.server.server_address
214 self.scheme = scheme
215- self.port = None
216- # use an http.HTTPFactory that was modified to ensure that we have
217- # clean close connections
218- self.site = server.Site(self.root, timeout=None)
219- self.wrapper = WrappingFactory(self.site)
220- self.wrapper.testserver_on_connection_lost = defer.Deferred()
221- self.wrapper.protocol = server_protocol_factory(self.wrapper.protocol)
222- self.wrapper._disconnecting = False
223
224- def listen(self, site):
225- """Listen a port to allow the tests."""
226- raise NotImplementedError('Base abstract class.')
227+ self.thread = threading.Thread(target=self.server.serve_forever)
228+ self.thread.daemon = True
229
230 def get_iri(self):
231 """Build the iri for this mock server."""
232 return "{scheme}://127.0.0.1:{port}/".format(scheme=self.scheme,
233- port=self.get_port())
234+ port=self.get_port())
235
236 def get_port(self):
237 """Return the port where we are listening."""
238- return self.port.getHost().port
239+ return self.port
240
241 def start(self):
242- """Start the service."""
243- self.port = self.listen(self.wrapper)
244+ """Start the web server in another thread."""
245+ self.thread.start()
246
247 def stop(self):
248- """Shut it down."""
249- if self.port:
250- self.wrapper._disconnecting = True
251- connected = self.wrapper.protocols.keys()
252- if connected:
253- for con in connected:
254- con.transport.loseConnection()
255- else:
256- self.wrapper.testserver_on_connection_lost = \
257- defer.succeed(None)
258- d = defer.maybeDeferred(self.port.stopListening)
259- return defer.gatherResults([d,
260- self.wrapper.testserver_on_connection_lost])
261- return defer.succeed(None)
262+ """Shutdown the web server."""
263+ self.server.shutdown()
264
265
266 class HTTPWebServer(BaseWebServer):
267- """A Webserver that listens to http connections."""
268-
269- def __init__(self, root_resource):
270- """Create a new instance."""
271- super(HTTPWebServer, self).__init__(root_resource, 'http')
272-
273- def listen(self, site):
274- """Listen a port to allow the tests."""
275- return reactor.listenTCP(0, site)
276-
277-
278-class HTTPSWebServer(BaseWebServer):
279- """A WebServer that listens to https connections."""
280-
281- def __init__(self, root_resource, ssl_settings=None):
282- """Create a new instance."""
283- super(HTTPSWebServer, self).__init__(root_resource, 'https')
284- self.ssl_settings = ssl_settings
285-
286- def listen(self, site):
287- """Listen a port to allow the tests."""
288- ssl_context = ssl.DefaultOpenSSLContextFactory(
289- self.ssl_settings['key'], self.ssl_settings['cert'])
290-
291- return reactor.listenSSL(0, site, ssl_context)
292+ """A standard library based HTTP server."""
293+
294+ def __init__(self, handler):
295+ super(HTTPWebServer, self).__init__(handler, scheme="http")
296+
297+
298+class HTTPSWebServer(HTTPWebServer):
299+ """A standard library based HTTPS server."""
300+
301+ def __init__(self, handler, ssl_settings):
302+ super(HTTPSWebServer, self).__init__(handler, scheme="https")
303+
304+ self.socket = ssl.wrap_socket(self.socket,
305+ keyfile=ssl_settings["key"],
306+ certfile=ssl_settings["key"],
307+ server_side=True)

Subscribers

People subscribed via source and target branches

to all changes: