mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Reverse the RPC socket connection: Python execution server connects to
Idle client and localhost origin of connection is verified by client. M PyShell.py M rpc.py M run.py
This commit is contained in:
parent
db40afaabe
commit
b417936d40
3 changed files with 110 additions and 71 deletions
|
@ -193,24 +193,30 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||||
def spawn_subprocess(self):
|
def spawn_subprocess(self):
|
||||||
port = 8833
|
port = 8833
|
||||||
addr = ("localhost", port)
|
addr = ("localhost", port)
|
||||||
|
# Spawn the Python execution "server"
|
||||||
w = ['-W' + s for s in sys.warnoptions]
|
w = ['-W' + s for s in sys.warnoptions]
|
||||||
args = [sys.executable] + w + ["-c", "__import__('run').main()",
|
args = [sys.executable] + w + ["-c", "__import__('run').main()",
|
||||||
str(port)]
|
str(port)]
|
||||||
self.rpcpid = os.spawnv(os.P_NOWAIT, args[0], args)
|
self.rpcpid = os.spawnv(os.P_NOWAIT, args[0], args)
|
||||||
for i in range(5):
|
# Idle starts listening for connection on localhost, retry since
|
||||||
|
# Idle may be restarted before port is available for rebinding
|
||||||
|
# XXX 25 July 2002 KBK Find out what is causing the delayed release!
|
||||||
|
for i in range(12):
|
||||||
time.sleep(i)
|
time.sleep(i)
|
||||||
try:
|
try:
|
||||||
self.rpcclt = rpc.RPCClient(addr)
|
self.rpcclt = rpc.RPCClient(addr)
|
||||||
break
|
break
|
||||||
except socket.error, err:
|
except socket.error, err:
|
||||||
if i > 3:
|
if i < 5:
|
||||||
print >>sys.__stderr__, "Socket error:", err, "; retry..."
|
print>>sys.__stderr__, ". ",
|
||||||
|
else:
|
||||||
|
print>>sys.__stderr__,"\nIdle socket error: " + err[1]\
|
||||||
|
+ ", retrying..."
|
||||||
else:
|
else:
|
||||||
# XXX Make this a dialog? #GvR
|
|
||||||
print >>sys.__stderr__, "Can't spawn subprocess!"
|
|
||||||
# XXX Add Stephen's error msg, resolve the two later... KBK 09Jun02
|
|
||||||
display_port_binding_error()
|
display_port_binding_error()
|
||||||
return
|
return
|
||||||
|
# Accept the connection from the Python execution server
|
||||||
|
self.rpcclt.accept()
|
||||||
self.rpcclt.register("stdin", self.tkconsole)
|
self.rpcclt.register("stdin", self.tkconsole)
|
||||||
self.rpcclt.register("stdout", self.tkconsole.stdout)
|
self.rpcclt.register("stdout", self.tkconsole.stdout)
|
||||||
self.rpcclt.register("stderr", self.tkconsole.stderr)
|
self.rpcclt.register("stderr", self.tkconsole.stderr)
|
||||||
|
|
|
@ -1,22 +1,33 @@
|
||||||
# ASCII-art documentation
|
"""RPC Implemention, originally written for the Python Idle IDE
|
||||||
#
|
|
||||||
# +---------------------------------+ +----------+
|
For security reasons, GvR requested that Idle's Python execution server process
|
||||||
# | SocketServer.BaseRequestHandler | | SocketIO |
|
connect to the Idle process, which listens for the connection. Since Idle has
|
||||||
# +---------------------------------+ +----------+
|
has only one client per server, this was not a limitation.
|
||||||
# ^ ^ ^
|
|
||||||
# | | |
|
+---------------------------------+ +-------------+
|
||||||
# | + -------------------+ |
|
| SocketServer.BaseRequestHandler | | SocketIO |
|
||||||
# | | |
|
+---------------------------------+ +-------------+
|
||||||
# +-------------------------+ +-----------------+
|
^ | register() |
|
||||||
# | RPCHandler | | RPCClient |
|
| | unregister()|
|
||||||
# |-------------------------| |-----------------|
|
| +-------------+
|
||||||
# | register() | | remotecall() |
|
| ^ ^
|
||||||
# | unregister() | | register() |
|
| | |
|
||||||
# | | | unregister() |
|
| + -------------------+ |
|
||||||
# | | | get_remote_proxy|
|
| | |
|
||||||
# +-------------------------+ +-----------------+
|
+-------------------------+ +-----------------+
|
||||||
#
|
| RPCHandler | | RPCClient |
|
||||||
import sys
|
| [attribute of RPCServer]| | |
|
||||||
|
+-------------------------+ +-----------------+
|
||||||
|
|
||||||
|
The RPCServer handler class is expected to provide register/unregister methods.
|
||||||
|
RPCHandler inherits the mix-in class SocketIO, which provides these methods.
|
||||||
|
|
||||||
|
See the Idle run.main() docstring for further information on how this was
|
||||||
|
accomplished in Idle.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
import socket
|
import socket
|
||||||
import select
|
import select
|
||||||
import SocketServer
|
import SocketServer
|
||||||
|
@ -55,40 +66,29 @@ class RPCServer(SocketServer.TCPServer):
|
||||||
def __init__(self, addr, handlerclass=None):
|
def __init__(self, addr, handlerclass=None):
|
||||||
if handlerclass is None:
|
if handlerclass is None:
|
||||||
handlerclass = RPCHandler
|
handlerclass = RPCHandler
|
||||||
# XXX KBK 25Jun02 Not used in Idlefork, see register/unregister note below.
|
# XXX KBK 25Jun02 Not used in Idlefork.
|
||||||
# self.objtable = objecttable
|
# self.objtable = objecttable
|
||||||
SocketServer.TCPServer.__init__(self, addr, handlerclass)
|
SocketServer.TCPServer.__init__(self, addr, handlerclass)
|
||||||
|
|
||||||
# XXX KBK 25Jun02 Following method is not used (yet)
|
def server_bind(self):
|
||||||
# def verify_request(self, request, client_address):
|
"Override TCPServer method, no bind() phase for connecting entity"
|
||||||
# host, port = client_address
|
pass
|
||||||
# if host != "127.0.0.1":
|
|
||||||
# print "Disallowed host:", host
|
|
||||||
# return 0
|
|
||||||
# else:
|
|
||||||
# return 1
|
|
||||||
|
|
||||||
# XXX KBK 25Jun02 The handlerclass is expected to provide register/unregister
|
def server_activate(self):
|
||||||
# methods. In Idle, RPCServer is instantiated with
|
"""Override TCPServer method, connect() instead of listen()
|
||||||
# handlerclass MyHandler, which in turn inherits the
|
|
||||||
# register/unregister methods from the mix-in class SocketIO.
|
Due to the reversed connection, self.server_address is actually the
|
||||||
# It is true that this is asymmetric with the RPCClient's use
|
address of the Idle Client to which we are connecting.
|
||||||
# of register/unregister, but I guess that's how a SocketServer
|
|
||||||
# is supposed to work.
|
|
||||||
|
|
||||||
# Exactly how this gets set up is convoluted. When the
|
"""
|
||||||
# TCPServer is instantiated, it creates an instance of
|
self.socket.connect(self.server_address)
|
||||||
# run.MyHandler and calls its handle() method. handle()
|
|
||||||
# instantiates a run.Executive, passing it a reference to the
|
def get_request(self):
|
||||||
# MyHandler object. That reference is saved as an attribute of
|
"Override TCPServer method, return already connected socket"
|
||||||
# the Executive instance. The Executive methods have access to
|
return self.socket, self.server_address
|
||||||
# the reference and can pass it on to entities that they
|
|
||||||
# command (e.g. RemoteDebugger.Debugger.start_debugger()). The
|
|
||||||
# latter, in turn, can call MyHandler(SocketIO)
|
|
||||||
# register/unregister methods via the reference to register and
|
|
||||||
# unregister themselves. Whew.
|
|
||||||
|
|
||||||
# The following two methods are not currently used in Idlefork.
|
# XXX The following two methods are not currently used in Idlefork.
|
||||||
# def register(self, oid, object):
|
# def register(self, oid, object):
|
||||||
# self.objtable[oid] = object
|
# self.objtable[oid] = object
|
||||||
|
|
||||||
|
@ -364,6 +364,8 @@ class SocketIO:
|
||||||
cv.notify()
|
cv.notify()
|
||||||
self.statelock.release()
|
self.statelock.release()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
#----------------- end class SocketIO --------------------
|
||||||
|
|
||||||
class RemoteObject:
|
class RemoteObject:
|
||||||
# Token mix-in class
|
# Token mix-in class
|
||||||
|
@ -388,15 +390,8 @@ class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
|
||||||
SocketIO.__init__(self, sock)
|
SocketIO.__init__(self, sock)
|
||||||
SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
|
SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
SocketServer.BaseRequestHandler.setup(self)
|
|
||||||
print >>sys.__stderr__, "Connection from", self.client_address
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
print >>sys.__stderr__, "End connection from", self.client_address
|
|
||||||
SocketServer.BaseRequestHandler.finish(self)
|
|
||||||
|
|
||||||
def handle(self):
|
def handle(self):
|
||||||
|
"handle() method required by SocketServer"
|
||||||
self.mainloop()
|
self.mainloop()
|
||||||
|
|
||||||
def get_remote_proxy(self, oid):
|
def get_remote_proxy(self, oid):
|
||||||
|
@ -404,12 +399,21 @@ class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
|
||||||
|
|
||||||
class RPCClient(SocketIO):
|
class RPCClient(SocketIO):
|
||||||
|
|
||||||
nextseq = 1 # Requests coming from the client are odd
|
nextseq = 1 # Requests coming from the client are odd numbered
|
||||||
|
|
||||||
def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
|
def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
|
||||||
sock = socket.socket(family, type)
|
self.sock = socket.socket(family, type)
|
||||||
sock.connect(address)
|
self.sock.bind(address)
|
||||||
SocketIO.__init__(self, sock)
|
self.sock.listen(1)
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
newsock, address = self.sock.accept()
|
||||||
|
if address[0] == '127.0.0.1':
|
||||||
|
print>>sys.__stderr__, "Idle accepted connection from ", address
|
||||||
|
SocketIO.__init__(self, newsock)
|
||||||
|
else:
|
||||||
|
print>>sys.__stderr__, "Invalid host: ", address
|
||||||
|
raise socket.error
|
||||||
|
|
||||||
def get_remote_proxy(self, oid):
|
def get_remote_proxy(self, oid):
|
||||||
return RPCProxy(self, oid)
|
return RPCProxy(self, oid)
|
||||||
|
@ -477,6 +481,7 @@ class MethodProxy:
|
||||||
#
|
#
|
||||||
|
|
||||||
def testServer(addr):
|
def testServer(addr):
|
||||||
|
# XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
|
||||||
class RemotePerson:
|
class RemotePerson:
|
||||||
def __init__(self,name):
|
def __init__(self,name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -505,10 +510,8 @@ def testServer(addr):
|
||||||
svr.handle_request() # process once only
|
svr.handle_request() # process once only
|
||||||
|
|
||||||
def testClient(addr):
|
def testClient(addr):
|
||||||
|
"demonstrates RPC Client"
|
||||||
#
|
# XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
|
||||||
# demonstrates RPC Client
|
|
||||||
#
|
|
||||||
import time
|
import time
|
||||||
clt=RPCClient(addr)
|
clt=RPCClient(addr)
|
||||||
thomas = clt.get_remote_proxy("thomas")
|
thomas = clt.get_remote_proxy("thomas")
|
||||||
|
@ -523,7 +526,6 @@ def testClient(addr):
|
||||||
print "Done."
|
print "Done."
|
||||||
print
|
print
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
# demonstrates remote server calling local instance
|
# demonstrates remote server calling local instance
|
||||||
class LocalPerson:
|
class LocalPerson:
|
||||||
def __init__(self,name):
|
def __init__(self,name):
|
||||||
|
|
|
@ -1,13 +1,44 @@
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
import rpc
|
import rpc
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
"""Start the Python execution server in a subprocess
|
||||||
|
|
||||||
|
In Idle, RPCServer is instantiated with handlerclass MyHandler, which
|
||||||
|
inherits register/unregister methods from RPCHandler via the mix-in class
|
||||||
|
SocketIO.
|
||||||
|
|
||||||
|
When the RPCServer is instantiated, the TCPServer initialization creates an
|
||||||
|
instance of run.MyHandler and calls its handle() method. handle()
|
||||||
|
instantiates a run.Executive, passing it a reference to the MyHandler
|
||||||
|
object. That reference is saved as an attribute of the Executive instance.
|
||||||
|
The Executive methods have access to the reference and can pass it on to
|
||||||
|
entities that they command (e.g. RemoteDebugger.Debugger.start_debugger()).
|
||||||
|
The latter, in turn, can call MyHandler(SocketIO) register/unregister
|
||||||
|
methods via the reference to register and unregister themselves.
|
||||||
|
|
||||||
|
"""
|
||||||
port = 8833
|
port = 8833
|
||||||
if sys.argv[1:]:
|
if sys.argv[1:]:
|
||||||
port = int(sys.argv[1])
|
port = int(sys.argv[1])
|
||||||
sys.argv[:] = [""]
|
sys.argv[:] = [""]
|
||||||
addr = ("localhost", port)
|
addr = ("localhost", port)
|
||||||
svr = rpc.RPCServer(addr, MyHandler)
|
for i in range(12):
|
||||||
|
time.sleep(i)
|
||||||
|
try:
|
||||||
|
svr = rpc.RPCServer(addr, MyHandler)
|
||||||
|
break
|
||||||
|
except socket.error, err:
|
||||||
|
if i < 5:
|
||||||
|
print>>sys.__stderr__, ".. ",
|
||||||
|
else:
|
||||||
|
print>>sys.__stderr__,"\nPython subprocess socket error: "\
|
||||||
|
+ err[1] + ", retrying...."
|
||||||
|
else:
|
||||||
|
print>>sys.__stderr__, "\nConnection to Idle failed, exiting."
|
||||||
|
sys.exit()
|
||||||
svr.handle_request() # A single request only
|
svr.handle_request() # A single request only
|
||||||
|
|
||||||
class MyHandler(rpc.RPCHandler):
|
class MyHandler(rpc.RPCHandler):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue