finish py3/async support, needs Twisted >= 15.5.0
The latest Twisted fixes the web.Agent code we need for proper async support. There's still a daemonization bug that prevents 'wormhole server start' from succeeding (it hangs).
This commit is contained in:
parent
f8fdec18a5
commit
80603aaa32
|
@ -6,7 +6,7 @@ python:
|
||||||
- "3.4"
|
- "3.4"
|
||||||
- "3.5"
|
- "3.5"
|
||||||
install:
|
install:
|
||||||
- pip install . Twisted pyflakes
|
- pip install . "Twisted>=15.5.0" pyflakes
|
||||||
script:
|
script:
|
||||||
- pyflakes src
|
- pyflakes src
|
||||||
- trial wormhole
|
- trial wormhole
|
||||||
|
|
|
@ -137,9 +137,9 @@ using a key derived from the PAKE phase. See
|
||||||
This library is released under the MIT license, see LICENSE for details.
|
This library is released under the MIT license, see LICENSE for details.
|
||||||
|
|
||||||
This library is compatible with python2.7, 3.3, 3.4, and 3.5 . It is probably
|
This library is compatible with python2.7, 3.3, 3.4, and 3.5 . It is probably
|
||||||
compatible with py2.6, but the latest Twisted (15.5.0) is not. The async
|
compatible with py2.6, but the latest Twisted (15.5.0) is not. The
|
||||||
support does not yet work with py3, but will in the future once Twisted
|
(daemonizing) 'wormhole server start' command does not yet work with py3, but
|
||||||
itself is finished being ported.
|
will in the future once Twisted itself is finished being ported.
|
||||||
|
|
||||||
This package depends upon the SPAKE2, pynacl, requests, and argparse
|
This package depends upon the SPAKE2, pynacl, requests, and argparse
|
||||||
libraries. To run a relay server, use the async support, or run the unit
|
libraries. To run a relay server, use the async support, or run the unit
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -22,6 +22,8 @@ setup(name="magic-wormhole",
|
||||||
["wormhole = wormhole.scripts.runner:entry"]},
|
["wormhole = wormhole.scripts.runner:entry"]},
|
||||||
install_requires=["spake2==0.3", "pynacl", "requests", "argparse",
|
install_requires=["spake2==0.3", "pynacl", "requests", "argparse",
|
||||||
"six"],
|
"six"],
|
||||||
|
# for Twisted support, we want Twisted>=15.5.0. Older Twisteds don't
|
||||||
|
# provide sufficient python3 compatibility.
|
||||||
test_suite="wormhole.test",
|
test_suite="wormhole.test",
|
||||||
cmdclass=commands,
|
cmdclass=commands,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys
|
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
from twisted.internet.defer import gatherResults
|
from twisted.internet.defer import gatherResults
|
||||||
from twisted.internet.threads import deferToThread
|
from twisted.internet.threads import deferToThread
|
||||||
|
@ -56,9 +55,3 @@ class Basic(ServerBase, unittest.TestCase):
|
||||||
return self.doBoth([bw.close], tw.close())
|
return self.doBoth([bw.close], tw.close())
|
||||||
d.addCallback(_done)
|
d.addCallback(_done)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
if sys.version_info[0] >= 3:
|
|
||||||
Basic.skip = "twisted is not yet sufficiently ported to py3"
|
|
||||||
# as of 15.4.0, Twisted is still missing:
|
|
||||||
# * web.client.Agent (for all non-EventSource POSTs in transcribe.py)
|
|
||||||
# * python.logfile (to allow daemonization of 'wormhole server')
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys, json
|
import json
|
||||||
import requests
|
import requests
|
||||||
from six.moves.urllib_parse import urlencode
|
from six.moves.urllib_parse import urlencode
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
|
@ -14,7 +14,7 @@ from ..twisted.eventsource_twisted import EventSource
|
||||||
class Reachable(ServerBase, unittest.TestCase):
|
class Reachable(ServerBase, unittest.TestCase):
|
||||||
|
|
||||||
def test_getPage(self):
|
def test_getPage(self):
|
||||||
# client.getPage requires str/unicode URL, returns bytes
|
# client.getPage requires bytes URL, returns bytes
|
||||||
url = self.relayurl.replace("wormhole-relay/", "").encode("ascii")
|
url = self.relayurl.replace("wormhole-relay/", "").encode("ascii")
|
||||||
d = getPage(url)
|
d = getPage(url)
|
||||||
def _got(res):
|
def _got(res):
|
||||||
|
@ -23,14 +23,9 @@ class Reachable(ServerBase, unittest.TestCase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_agent(self):
|
def test_agent(self):
|
||||||
# client.Agent is not yet ported: it wants URLs to be both unicode
|
|
||||||
# and bytes at the same time.
|
|
||||||
# https://twistedmatrix.com/trac/ticket/7407
|
|
||||||
if sys.version_info[0] > 2:
|
|
||||||
raise unittest.SkipTest("twisted.web.client.Agent does not yet support py3")
|
|
||||||
url = self.relayurl.replace("wormhole-relay/", "").encode("ascii")
|
url = self.relayurl.replace("wormhole-relay/", "").encode("ascii")
|
||||||
agent = Agent(reactor)
|
agent = Agent(reactor)
|
||||||
d = agent.request("GET", url)
|
d = agent.request(b"GET", url)
|
||||||
def _check(resp):
|
def _check(resp):
|
||||||
self.failUnlessEqual(resp.code, 200)
|
self.failUnlessEqual(resp.code, 200)
|
||||||
return readBody(resp)
|
return readBody(resp)
|
||||||
|
@ -281,9 +276,6 @@ class API(ServerBase, unittest.TestCase):
|
||||||
return self._do_watch("watch")
|
return self._do_watch("watch")
|
||||||
|
|
||||||
def _do_watch(self, endpoint_name):
|
def _do_watch(self, endpoint_name):
|
||||||
if sys.version_info[0] >= 3:
|
|
||||||
raise unittest.SkipTest("twisted vs py3")
|
|
||||||
|
|
||||||
d = self.post("allocate", {"appid": "app1", "side": "abc"})
|
d = self.post("allocate", {"appid": "app1", "side": "abc"})
|
||||||
def _allocated(data):
|
def _allocated(data):
|
||||||
self.cid = data["channelid"]
|
self.cid = data["channelid"]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import sys, json
|
import json
|
||||||
from twisted.trial import unittest
|
from twisted.trial import unittest
|
||||||
from twisted.internet.defer import gatherResults, succeed
|
from twisted.internet.defer import gatherResults, succeed
|
||||||
from ..twisted.transcribe import (Wormhole, UsageError, ChannelManager,
|
from ..twisted.transcribe import (Wormhole, UsageError, ChannelManager,
|
||||||
|
@ -389,7 +389,7 @@ class Basic(ServerBase, unittest.TestCase):
|
||||||
d.addCallback(_done)
|
d.addCallback(_done)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
data1 = u"""\
|
data1 = b"""\
|
||||||
event: welcome
|
event: welcome
|
||||||
data: one and a
|
data: one and a
|
||||||
data: two
|
data: two
|
||||||
|
@ -420,10 +420,9 @@ class EventSourceClient(unittest.TestCase):
|
||||||
(u"e2", u"four"),
|
(u"e2", u"four"),
|
||||||
])
|
])
|
||||||
|
|
||||||
if sys.version_info[0] >= 3:
|
# new py3 support in 15.5.0: web.client.Agent, w.c.downloadPage, twistd
|
||||||
Channel.skip = "twisted is not yet sufficiently ported to py3"
|
|
||||||
Basic.skip = "twisted is not yet sufficiently ported to py3"
|
# However trying 'wormhole server start' with py3/twisted-15.5.0 throws an
|
||||||
EventSourceClient.skip = "twisted is not yet sufficiently ported to py3"
|
# error in t.i._twistd_unix.UnixApplicationRunner.postApplication, it calls
|
||||||
# as of 15.4.0, Twisted is still missing:
|
# os.write with str, not bytes. This file does not cover that test (testing
|
||||||
# * web.client.Agent (for all non-EventSource POSTs in transcribe.py)
|
# daemonization is hard).
|
||||||
# * python.logfile (to allow daemonization of 'wormhole server')
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ from ..util.eventual import eventually
|
||||||
|
|
||||||
class EventSourceParser(basic.LineOnlyReceiver):
|
class EventSourceParser(basic.LineOnlyReceiver):
|
||||||
# http://www.w3.org/TR/eventsource/
|
# http://www.w3.org/TR/eventsource/
|
||||||
delimiter = "\n"
|
delimiter = b"\n"
|
||||||
|
|
||||||
def __init__(self, handler):
|
def __init__(self, handler):
|
||||||
self.current_field = None
|
self.current_field = None
|
||||||
|
@ -97,8 +97,8 @@ class EventSource: # TODO: service.Service
|
||||||
assert not self.started, "single-use"
|
assert not self.started, "single-use"
|
||||||
self.started = True
|
self.started = True
|
||||||
assert self.url
|
assert self.url
|
||||||
d = self.agent.request("GET", self.url.encode("utf-8"),
|
d = self.agent.request(b"GET", self.url.encode("utf-8"),
|
||||||
Headers({"accept": ["text/event-stream"]}))
|
Headers({b"accept": [b"text/event-stream"]}))
|
||||||
d.addCallback(self._connected)
|
d.addCallback(self._connected)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class DataProducer:
|
||||||
def post_json(agent, url, request_body):
|
def post_json(agent, url, request_body):
|
||||||
# POST a JSON body to a URL, parsing the response as JSON
|
# POST a JSON body to a URL, parsing the response as JSON
|
||||||
data = json.dumps(request_body).encode("utf-8")
|
data = json.dumps(request_body).encode("utf-8")
|
||||||
d = agent.request("POST", url.encode("utf-8"),
|
d = agent.request(b"POST", url.encode("utf-8"),
|
||||||
bodyProducer=DataProducer(data))
|
bodyProducer=DataProducer(data))
|
||||||
def _check_error(resp):
|
def _check_error(resp):
|
||||||
if resp.code != 200:
|
if resp.code != 200:
|
||||||
|
@ -53,19 +53,19 @@ def post_json(agent, url, request_body):
|
||||||
return resp
|
return resp
|
||||||
d.addCallback(_check_error)
|
d.addCallback(_check_error)
|
||||||
d.addCallback(web_client.readBody)
|
d.addCallback(web_client.readBody)
|
||||||
d.addCallback(lambda data: json.loads(data))
|
d.addCallback(lambda data: json.loads(data.decode("utf-8")))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def get_json(agent, url):
|
def get_json(agent, url):
|
||||||
# GET from a URL, parsing the response as JSON
|
# GET from a URL, parsing the response as JSON
|
||||||
d = agent.request("GET", url.encode("utf-8"))
|
d = agent.request(b"GET", url.encode("utf-8"))
|
||||||
def _check_error(resp):
|
def _check_error(resp):
|
||||||
if resp.code != 200:
|
if resp.code != 200:
|
||||||
raise web_error.Error(resp.code, resp.phrase)
|
raise web_error.Error(resp.code, resp.phrase)
|
||||||
return resp
|
return resp
|
||||||
d.addCallback(_check_error)
|
d.addCallback(_check_error)
|
||||||
d.addCallback(web_client.readBody)
|
d.addCallback(web_client.readBody)
|
||||||
d.addCallback(lambda data: json.loads(data))
|
d.addCallback(lambda data: json.loads(data.decode("utf-8")))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
class Channel:
|
class Channel:
|
||||||
|
@ -100,6 +100,7 @@ class Channel:
|
||||||
if not isinstance(phase, type(u"")): raise TypeError(type(phase))
|
if not isinstance(phase, type(u"")): raise TypeError(type(phase))
|
||||||
if not isinstance(msg, type(b"")): raise TypeError(type(msg))
|
if not isinstance(msg, type(b"")): raise TypeError(type(msg))
|
||||||
self._sent_messages.add( (phase,msg) )
|
self._sent_messages.add( (phase,msg) )
|
||||||
|
assert isinstance(self._side, type(u"")), type(self._side)
|
||||||
payload = {"appid": self._appid,
|
payload = {"appid": self._appid,
|
||||||
"channelid": self._channelid,
|
"channelid": self._channelid,
|
||||||
"side": self._side,
|
"side": self._side,
|
||||||
|
@ -321,8 +322,8 @@ class Wormhole:
|
||||||
"relay_url": self._relay_url,
|
"relay_url": self._relay_url,
|
||||||
"code": self.code,
|
"code": self.code,
|
||||||
"side": self._side,
|
"side": self._side,
|
||||||
"spake2": json.loads(self.sp.serialize()),
|
"spake2": json.loads(self.sp.serialize().decode("ascii")),
|
||||||
"msg1": self.msg1.encode("hex"),
|
"msg1": hexlify(self.msg1).decode("ascii"),
|
||||||
}
|
}
|
||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
|
|
||||||
|
@ -330,10 +331,11 @@ class Wormhole:
|
||||||
def from_serialized(klass, data):
|
def from_serialized(klass, data):
|
||||||
d = json.loads(data)
|
d = json.loads(data)
|
||||||
self = klass(d["appid"], d["relay_url"])
|
self = klass(d["appid"], d["relay_url"])
|
||||||
self._set_side(d["side"].encode("ascii"))
|
self._set_side(d["side"])
|
||||||
self._set_code_and_channelid(d["code"])
|
self._set_code_and_channelid(d["code"])
|
||||||
self.sp = SPAKE2_Symmetric.from_serialized(json.dumps(d["spake2"]))
|
sp_data = json.dumps(d["spake2"]).encode("ascii")
|
||||||
self.msg1 = d["msg1"].decode("hex")
|
self.sp = SPAKE2_Symmetric.from_serialized(sp_data)
|
||||||
|
self.msg1 = unhexlify(d["msg1"].encode("ascii"))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def derive_key(self, purpose, length=SecretBox.KEY_SIZE):
|
def derive_key(self, purpose, length=SecretBox.KEY_SIZE):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user