Skip to content

Commit 4880bbb

Browse files
committed
Cleanup demo server code and add inheritable classes
1 parent b7a69fb commit 4880bbb

File tree

7 files changed

+184
-142
lines changed

7 files changed

+184
-142
lines changed

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ arithmetic operations. To run the demo server, simply run::
4343
4444
python examples/demo_server.py
4545

46+
It furthermore contains a slightly more advanced server used within
47+
the OpenDreamKit project. To use it, run:
48+
49+
python mitm/converting_server.py
50+
4651

4752
Client
4853
------

examples/demo_server.py

Lines changed: 15 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import socket
2-
from six.moves import socketserver
31
import logging
2+
3+
from six.moves import socketserver
4+
45
from openmath import openmath as om, convert as conv
56

6-
from scscp.client import TimeoutError, CONNECTED
7-
from scscp.server import SCSCPServer
8-
from scscp.scscp import SCSCPQuit, SCSCPProtocolError
97
from scscp import scscp
8+
from scscp.socketserver import SCSCPServerRequestHandler, SCSCPSocketServer, CD_SCSCP2
109

1110
# Supported functions
12-
CD_SCSCP2 = ['get_service_description', 'get_allowed_heads', 'is_allowed_head']
1311
CD_ARITH1 = {
1412
'abs' : abs,
1513
'unary_minus' : lambda x : -x,
@@ -20,61 +18,13 @@
2018
'power' : lambda x,y: x**y,
2119
}
2220

23-
class SCSCPRequestHandler(socketserver.BaseRequestHandler):
24-
def setup(self):
25-
self.server.log.info("New connection from %s:%d" % self.client_address)
26-
self.log = self.server.log.getChild(self.client_address[0])
27-
self.scscp = SCSCPServer(self.request, self.server.name,
28-
self.server.version, logger=self.log)
21+
class DemoServerRequestHandler(SCSCPServerRequestHandler):
22+
def handle_call(self, call, head):
23+
if call.data.elem.cd == 'arith1' and head in CD_ARITH1:
24+
args = [conv.to_python(a) for a in call.data.arguments]
25+
return conv.to_openmath(CD_ARITH1[head](*args))
2926

30-
def handle(self):
31-
self.scscp.accept()
32-
while True:
33-
try:
34-
call = self.scscp.wait()
35-
except TimeoutError:
36-
continue
37-
except SCSCPQuit as e:
38-
self.log.info(e)
39-
break
40-
except ConnectionResetError:
41-
self.log.info('Client closed unexpectedly.')
42-
break
43-
except SCSCPProtocolError as e:
44-
self.log.info('SCSCP protocol error: %s.' % str(e))
45-
self.log.info('Closing connection.')
46-
self.scscp.quit()
47-
break
48-
self.handle_call(call)
49-
50-
def handle_call(self, call):
51-
if (call.type != 'procedure_call'):
52-
raise SCSCPProtocolError('Bad message from client: %s.' % call.type, om=call.om())
53-
try:
54-
head = call.data.elem.name
55-
self.log.debug('Requested head: %s...' % head)
56-
57-
if call.data.elem.cd == 'scscp2' and head in CD_SCSCP2:
58-
res = getattr(self, head)(call.data)
59-
elif call.data.elem.cd == 'arith1' and head in CD_ARITH1:
60-
args = [conv.to_python(a) for a in call.data.arguments]
61-
res = conv.to_openmath(CD_ARITH1[head](*args))
62-
else:
63-
self.log.debug('...head unknown.')
64-
return self.scscp.terminated(call.id, om.OMError(
65-
om.OMSymbol('unhandled_symbol', cd='error'), [call.data.elem]))
66-
67-
strlog = str(res)
68-
self.log.debug('...sending result: %s' % (strlog[:20] + ('...' if len(strlog) > 20 else '')))
69-
return self.scscp.completed(call.id, res)
70-
except (AttributeError, IndexError, TypeError):
71-
self.log.debug('...client protocol error.')
72-
return self.scscp.terminated(call.id, om.OMError(
73-
om.OMSymbol('unexpected_symbol', cd='error'), [call.data]))
74-
except Exception as e:
75-
self.log.exception('Unhandled exception:')
76-
return self.scscp.terminated(call.id, 'system_specific',
77-
'Unhandled exception %s.' % str(e))
27+
return super(SCSCPRequestHandler, self).handle_call(call, head)
7828

7929
def get_allowed_heads(self, data):
8030
return scscp.symbol_set([om.OMSymbol(head, cd='scscp2') for head in CD_SCSCP2]
@@ -92,17 +42,14 @@ def get_service_description(self, data):
9242
self.server.version.decode(),
9343
self.server.description)
9444

95-
class Server(socketserver.ThreadingMixIn, socketserver.TCPServer, object):
96-
allow_reuse_address = True
97-
45+
class Server(SCSCPSocketServer):
9846
def __init__(self, host='localhost', port=26133,
9947
logger=None, name=b'DemoServer', version=b'none',
10048
description='Demo SCSCP server'):
101-
super(Server, self).__init__((host, port), SCSCPRequestHandler)
102-
self.log = logger or logging.getLogger(__name__)
103-
self.name = name
104-
self.version = version
105-
self.description = description
49+
50+
super(Server, self).__init__(host=host, port=port, logger=logger or logging.getLogger(__name__),
51+
name=name, version=version, description=description,
52+
RequestHandlerClass=DemoServerRequestHandler)
10653

10754
if __name__ == '__main__':
10855
logging.basicConfig(level=logging.DEBUG)

mitm/converting_server.py

Lines changed: 19 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,73 +9,25 @@
99
from scscp.scscp import SCSCPQuit, SCSCPProtocolError
1010
from scscp import scscp
1111

12+
from scscp.socketserver import SCSCPServerRequestHandler, SCSCPSocketServer
13+
1214
MitMBase = "http://opendreamkit.org/MitM"
1315
MitMCD = "computation"
1416
MitMEval = "sage_eval"
1517

16-
class MitMRequestHandler(socketserver.BaseRequestHandler):
17-
def __init__(self,converter, *kargs, **kwargs):
18+
class MitMRequestHandler(SCSCPServerRequestHandler):
19+
def __init__(self, converter, *args, **kwargs):
20+
super(MitMRequestHandler, self).__init__(*args, **kwargs)
1821
self.converter = converter
19-
super(socketserver.BaseRequestHandler, self).__init__(*kargs, **kwargs)
2022

21-
def setup(self):
22-
self.server.log.info("New connection from %s:%d" % self.client_address)
23-
self.log = self.server.log.getChild(self.client_address[0])
24-
self.scscp = SCSCPServer(self.request, self.server.name,
25-
self.server.version, logger=self.log)
26-
27-
# TODO: should be inherited from a to-be-written SCSCPRequestHandler class
28-
def handle(self):
29-
self.scscp.accept()
30-
while True:
31-
try:
32-
call = self.scscp.wait()
33-
except TimeoutError:
34-
continue
35-
except SCSCPQuit as e:
36-
self.log.info(e)
37-
break
38-
except ConnectionResetError:
39-
self.log.info('Client closed unexpectedly.')
40-
break
41-
except SCSCPProtocolError as e:
42-
self.log.info('SCSCP protocol error: %s.' % str(e))
43-
self.log.info('Closing connection.')
44-
self.scscp.quit()
45-
break
46-
self.handle_call(call)
47-
48-
# TODO: most of this should be inherited as well
49-
def handle_call(self, call):
50-
if (call.type != 'procedure_call'):
51-
raise SCSCPProtocolError('Bad message from client: %s.' % call.type, om=call.om())
52-
try:
53-
head = call.data.elem.name
54-
self.log.debug('Requested head: %s...' % head)
55-
56-
if call.data.elem.cd == 'scscp2' and head in CD_SCSCP2:
57-
res = getattr(self, head)(call.data)
58-
elif self.data.elem.base == MitMBase and self.data.elem.cd == MitMCD and self.data.elem.name == MitMEval:
23+
def handle_call(self, call, head):
24+
if self.data.elem.base == MitMBase and self.data.elem.cd == MitMCD and self.data.elem.name == MitMEval:
5925
# we take the one argument of MitMEval, import it (which triggers computation), and export it (i.e., the result of the computation)
6026
obj = call.data.arguments[0]
6127
objPy = self.converter.to_python(obj)
62-
res = self.converter.to_openmath(objPy)
63-
else:
64-
self.log.debug('...head unknown.')
65-
return self.scscp.terminated(call.id, om.OMError(
66-
om.OMSymbol('unhandled_symbol', cd='error'), [call.data.elem]))
67-
68-
strlog = str(res)
69-
self.log.debug('...sending result: %s' % (strlog[:20] + ('...' if len(strlog) > 20 else '')))
70-
return self.scscp.completed(call.id, res)
71-
except (AttributeError, IndexError, TypeError):
72-
self.log.debug('...client protocol error.')
73-
return self.scscp.terminated(call.id, om.OMError(
74-
om.OMSymbol('unexpected_symbol', cd='error'), [call.data]))
75-
except Exception as e:
76-
self.log.exception('Unhandled exception:')
77-
return self.scscp.terminated(call.id, 'system_specific',
78-
'Unhandled exception %s.' % str(e))
28+
return self.converter.to_openmath(objPy)
29+
30+
return super(MitMRequestHandler, self).handle_call(call, head)
7931

8032
def get_allowed_heads(self, data):
8133
return scscp.symbol_set([om.OMSymbol(base = MitMEval, cd = MitMCD, name = MitMEval)], cdnames=[MitMCD, 'scscp1'])
@@ -90,27 +42,27 @@ def get_service_description(self, data):
9042
self.server.version.decode(),
9143
self.server.description)
9244

93-
class MitMSCSCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer, object):
94-
allow_reuse_address = True
95-
45+
class MitMSCSCPServer(SCSCPSocketServer):
9646
def __init__(self, openmath_converter, host='localhost', port=26133,
9747
logger=None, name=b'MitM Server', version=b'none',
9848
description='MitM SCSCP server'):
99-
# ThreadingMixIn expects a class as constructor argument, so we have to build one that knows about the converter
49+
50+
# build a converter class
10051
class ReqHandler(MitMRequestHandler):
10152
def __init__(self, *args, **kwargs):
10253
super(MitMRequestHandler,self).__init__(openmath_converter, *args, **kwargs)
103-
super(MitMSCSCPServer, self).__init__((host, port), ReqHandler)
104-
self.log = logger or logging.getLogger(__name__)
105-
self.name = name
106-
self.version = version
107-
self.description = description
54+
55+
super(MitMSCSCPServer, self).__init__(host=host, port=port,
56+
logger=logger or logging.getLogger(__name__), name=name, version=version,
57+
description=description, RequestHandlerClass=ReqHandler)
10858

10959
if __name__ == '__main__':
11060
logging.basicConfig(level=logging.DEBUG)
11161
logger = logging.getLogger('demo_server')
62+
11263
conv = MitMConverter()
11364
srv = MitMSCSCPServer(conv, logger=logger)
65+
11466
try:
11567
srv.serve_forever()
11668
except KeyboardInterrupt:

scscp/client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22
from pexpect import fdpexpect, TIMEOUT, EOF
33
from openmath import encoder, decoder
4-
from . import scscp
54
from .scscp import SCSCPConnectionError, SCSCPQuit, SCSCPCancel, SCSCPProcedureMessage
65
from .processing_instruction import ProcessingInstruction as PI, OrderedProcessingInstruction as OPI
76

scscp/scscp.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class SCSCPProtocolError(SCSCPError):
1717
def __init__(self, msg, om=None):
1818
super(SCSCPProtocolError, self).__init__(msg)
1919
self.om = om
20+
class SCSCPUnknownHead(SCSCPError):
21+
pass
2022

2123
### SCSCP1 content dictionary
2224

scscp/server.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
from .client import SCSCPPeer, SCSCPPeerOM, _assert_status, INITIALIZED, CONNECTED
33
from .scscp import SCSCPConnectionError, SCSCPProcedureMessage
44

5+
56
class SCSCPServerBase(SCSCPPeer):
67
"""
78
A simple SCSCP synchronous server, with no understanding of OpenMath.
89
"""
9-
10+
1011
def __init__(self, socket, name, version, id=None, timeout=30, logger=None):
11-
super(SCSCPServerBase, self).__init__(socket, timeout, logger, me="Server", you="Client")
12+
super(SCSCPServerBase, self).__init__(
13+
socket, timeout, logger, me="Server", you="Client")
1214
self._name = name
1315
self._version = version
1416
self._id = id or str(uuid.uuid1()).encode()
@@ -17,22 +19,23 @@ def __init__(self, socket, name, version, id=None, timeout=30, logger=None):
1719
def accept(self, timeout=None):
1820
""" SCSCP handshake """
1921
self._send_ordered_PI('', [('service_name', self._name), ('service_version', self._version),
20-
('service_id', self._id), ('scscp_versions', b'1.3')])
21-
22+
('service_id', self._id), ('scscp_versions', b'1.3')])
23+
2224
pi = self._get_next_PI([''], timeout=timeout)
2325
if pi.attrs.get('version') != b'1.3':
2426
self.quit()
2527
raise SCSCPConnectionError("Client sent unexpected response.", pi)
26-
28+
2729
self._send_PI(version=b'1.3')
2830

2931
self.status = CONNECTED
3032

31-
33+
3234
class SCSCPServer(SCSCPServerBase, SCSCPPeerOM):
3335
"""
3436
A simple SCSCP synchronous server.
3537
"""
38+
3639
def wait(self, timeout=-1):
3740
return SCSCPProcedureMessage.from_om(self.receive(timeout))
3841

0 commit comments

Comments
 (0)