Stub out websocket read loop and Frame language construct

This commit is contained in:
Aldo Cortesi 2015-04-29 10:02:16 +12:00
parent 5405a4d458
commit 7e69fab331
3 changed files with 141 additions and 33 deletions

View File

@ -267,7 +267,7 @@ class _Token(object):
"""
return None
def resolve(self, msg, settings):
def resolve(self, settings, msg):
"""
Resolves this token to ready it for transmission. This means that
the calculated offsets of actions are fixed.
@ -371,8 +371,6 @@ class ValueFile(_Token):
def get_generator(self, settings):
if not settings.staticdir:
raise FileAccessDenied("File access disabled.")
sd = os.path.normpath(os.path.abspath(settings.staticdir))
s = os.path.expanduser(self.path)
s = os.path.normpath(
os.path.abspath(os.path.join(settings.staticdir, s))
@ -613,26 +611,34 @@ class Path(_Component):
return Path(self.value.freeze(settings))
class WS(_Component):
class _Token(_Component):
def __init__(self, value):
self.value = value
@classmethod
def expr(klass):
spec = pp.CaselessLiteral("ws")
spec = pp.CaselessLiteral(klass.TOK)
spec = spec.setParseAction(lambda x: klass(*x))
return spec
def values(self, settings):
return "ws"
return self.TOK
def spec(self):
return "ws"
return self.TOK
def freeze(self, settings):
return self
class WS(_Token):
TOK = "ws"
class WF(_Token):
TOK = "wf"
class Method(_Component):
methods = [
"get",
@ -724,7 +730,7 @@ class _Action(_Token):
def __init__(self, offset):
self.offset = offset
def resolve(self, msg, settings):
def resolve(self, settings, msg):
"""
Resolves offset specifications to a numeric offset. Returns a copy
of the action object.
@ -889,10 +895,6 @@ class _Message(object):
l += len(i.value.get_generator(settings))
return l
@abc.abstractmethod
def preamble(self, settings): # pragma: no cover
pass
@classmethod
def expr(klass): # pragma: no cover
pass
@ -929,6 +931,10 @@ Sep = pp.Optional(pp.Literal(":")).suppress()
class _HTTPMessage(_Message):
version = "HTTP/1.1"
@abc.abstractmethod
def preamble(self, settings): # pragma: no cover
pass
def values(self, settings):
vals = self.preamble(settings)
@ -985,7 +991,7 @@ class Response(_HTTPMessage):
)
return l
def resolve(self, settings):
def resolve(self, settings, msg=None):
tokens = self.tokens[:]
if self.ws:
if not settings.websocket_key:
@ -1017,7 +1023,7 @@ class Response(_HTTPMessage):
)
intermediate = self.__class__(tokens)
return self.__class__(
[i.resolve(intermediate, settings) for i in tokens]
[i.resolve(settings, intermediate) for i in tokens]
)
@classmethod
@ -1035,6 +1041,7 @@ class Response(_HTTPMessage):
pp.ZeroOrMore(Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def spec(self):
@ -1081,7 +1088,7 @@ class Request(_HTTPMessage):
v.append(self.version)
return v
def resolve(self, settings):
def resolve(self, settings, msg=None):
tokens = self.tokens[:]
if self.ws:
if not self.method:
@ -1114,7 +1121,7 @@ class Request(_HTTPMessage):
)
intermediate = self.__class__(tokens)
return self.__class__(
[i.resolve(intermediate, settings) for i in tokens]
[i.resolve(settings, intermediate) for i in tokens]
)
@classmethod
@ -1134,6 +1141,7 @@ class Request(_HTTPMessage):
pp.ZeroOrMore(Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def spec(self):
@ -1155,13 +1163,27 @@ class WebsocketFrame(_Message):
atom = pp.MatchFirst(parts)
resp = pp.And(
[
pp.Literal("wf"),
WF.expr(),
Sep,
pp.ZeroOrMore(Sep + atom)
]
)
resp = resp.setParseAction(klass)
return resp
def values(self, settings):
vals = [
websockets.FrameHeader().to_bytes()
]
if self.body:
vals.append(self.body.value.get_generator(settings))
return vals
def resolve(self, settings, msg=None):
return self.__class__(
[i.resolve(settings, msg) for i in self.tokens]
)
def spec(self):
return ":".join([i.spec() for i in self.tokens])
@ -1205,7 +1227,7 @@ def parse_response(s):
except UnicodeError:
raise ParseException("Spec must be valid ASCII.", 0, 0)
try:
return Response(Response.expr().parseString(s, parseAll=True))
return Response.expr().parseString(s, parseAll=True)[0]
except pp.ParseException, v:
raise ParseException(v.msg, v.line, v.col)
@ -1219,16 +1241,13 @@ def parse_requests(s):
except UnicodeError:
raise ParseException("Spec must be valid ASCII.", 0, 0)
try:
parts = pp.OneOrMore(
pp.Group(
pp.Or(
[
Request.expr(),
WebsocketFrame.expr(),
]
)
return pp.OneOrMore(
pp.Or(
[
WebsocketFrame.expr(),
Request.expr(),
]
)
).parseString(s, parseAll=True)
return [Request(i) for i in parts]
except pp.ParseException, v:
raise ParseException(v.msg, v.line, v.col)

View File

@ -3,10 +3,11 @@ import os
import hashlib
import random
import time
import threading
import OpenSSL.crypto
from netlib import tcp, http, certutils
from netlib import tcp, http, certutils, websockets
import netlib.utils
import language
@ -75,6 +76,17 @@ class Response:
return "Response(%s - %s)"%(self.status_code, self.msg)
class WebsocketFrameReader(threading.Thread):
def __init__(self, rfile, callback):
threading.Thread.__init__(self)
self.rfile, self.callback = rfile, callback
self.daemon = True
def run(self):
while 1:
print websockets.Frame.from_file(self.rfile)
class Pathoc(tcp.TCPClient):
def __init__(
self,
@ -184,13 +196,64 @@ class Pathoc(tcp.TCPClient):
print >> fp, "%s (unprintables escaped):"%header
print >> fp, netlib.utils.cleanBin(data)
def request(self, r):
def websocket_get_frame(self, frame):
"""
Called when a frame is received from the server.
"""
pass
def websocket_send_frame(self, r):
"""
Sends a single websocket frame.
"""
req = None
if isinstance(r, basestring):
r = language.parse_requests(r)[0]
if self.showreq:
self.wfile.start_log()
try:
req = language.serve(r, self.wfile, self.settings)
self.wfile.flush()
except tcp.NetLibTimeout:
if self.ignoretimeout:
return None
if self.showsummary:
print >> self.fp, "<<", "Timeout"
raise
except tcp.NetLibDisconnect: # pragma: nocover
if self.showsummary:
print >> self.fp, "<<", "Disconnect"
raise
finally:
if req:
if self.explain:
print >> self.fp, ">> Spec:", r.spec()
if self.showreq:
self._show(
self.fp, ">> Request",
self.wfile.get_log(),
self.hexdump
)
def websocket_start(self, r, callback=None):
"""
Performs an HTTP request, and attempts to drop into websocket
connection.
"""
resp = self.http(r)
if resp.status_code == 101:
if self.showsummary:
print >> self.fp, "Websocket connection established..."
WebsocketFrameReader(self.rfile, self.websocket_get_frame).start()
return resp
def http(self, r):
"""
Performs a single request.
r: A language.Request object, or a string representing one request.
Returns True if we have a non-ignored response.
Returns Response if we have a non-ignored response.
May raise http.HTTPError, tcp.NetLibError
"""
@ -253,6 +316,26 @@ class Pathoc(tcp.TCPClient):
)
return resp
def request(self, r):
"""
Performs a single request.
r: A language.Request object, or a string representing one request.
Returns Response if we have a non-ignored response.
May raise http.HTTPError, tcp.NetLibError
"""
if isinstance(r, basestring):
r = language.parse_requests(r)[0]
if isinstance(r, language.Request):
if r.ws:
return self.websocket_start(r, self.websocket_get_frame)
else:
return self.http(r)
elif isinstance(r, language.WebsocketFrame):
self.websocket_send_frame(r)
def main(args): # pragma: nocover
memo = set([])

View File

@ -406,7 +406,7 @@ class Test_Action:
def test_resolve(self):
r = parse_request('GET:"/foo"')
e = language.DisconnectAt("r")
ret = e.resolve(r, {})
ret = e.resolve({}, r)
assert isinstance(ret.offset, int)
def test_repr(self):
@ -637,12 +637,18 @@ class TestRequest:
assert utils.get_header("Upgrade", res.headers).value.val == "websocket"
class TestWebsocketFrame:
def test_spec(self):
e = language.WebsocketFrame.expr()
assert e.parseString("wf:foo")
wf = e.parseString("wf:b'foo'")
assert wf
assert parse_request("wf:b'foo'")
def test_values(self):
r = parse_request("wf:b'foo'")
assert r.values(language.Settings())
class TestWriteValues: