Sun, 09 May 2021 14:53:27 +0200
E5Network
- added base classes for a JSON based server and client (examples see rope and jedi plug-in)
--- a/eric6.epj Sun May 09 14:34:26 2021 +0200 +++ b/eric6.epj Sun May 09 14:53:27 2021 +0200 @@ -2275,7 +2275,9 @@ "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Simplify/SimplifyNodeVisitor.py", "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsFunctionVisitor.py", "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsEnums.py", - "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsCheckerDefaults.py" + "eric6/Plugins/CheckerPlugins/CodeStyleChecker/Annotations/AnnotationsCheckerDefaults.py", + "eric6/E5Network/E5JsonClient.py", + "eric6/E5Network/E5JsonServer.py" ], "SPELLEXCLUDES": "Dictionaries/excludes.dic", "SPELLLANGUAGE": "en_US",
--- a/eric6/APIs/Python3/eric6.api Sun May 09 14:34:26 2021 +0200 +++ b/eric6/APIs/Python3/eric6.api Sun May 09 14:53:27 2021 +0200 @@ -1680,6 +1680,18 @@ eric6.E5Network.E5GoogleMailHelpers.TOKEN_FILE?7 eric6.E5Network.E5GoogleMailHelpers.getInstallCommand?4() eric6.E5Network.E5GoogleMailHelpers.isClientSecretFileAvailable?4() +eric6.E5Network.E5JsonClient.E5JsonClient.handleCall?4(method, params) +eric6.E5Network.E5JsonClient.E5JsonClient.run?4() +eric6.E5Network.E5JsonClient.E5JsonClient.sendJson?4(command, params) +eric6.E5Network.E5JsonClient.E5JsonClient?1(host, port, idString="") +eric6.E5Network.E5JsonServer.E5JsonServer.connectionNames?4() +eric6.E5Network.E5JsonServer.E5JsonServer.handleCall?4(method, params) +eric6.E5Network.E5JsonServer.E5JsonServer.handleNewConnection?4() +eric6.E5Network.E5JsonServer.E5JsonServer.sendJson?4(command, params, flush=False, idString="") +eric6.E5Network.E5JsonServer.E5JsonServer.startClient?4(interpreter, clientScript, clientArgs, idString="", environment=None) +eric6.E5Network.E5JsonServer.E5JsonServer.stopAllClients?4() +eric6.E5Network.E5JsonServer.E5JsonServer.stopClient?4(idString="") +eric6.E5Network.E5JsonServer.E5JsonServer?1(name="", multiplex=False, parent=None) eric6.E5Network.E5NetworkHeaderDetailsDialog.E5NetworkHeaderDetailsDialog.setData?4(name, value) eric6.E5Network.E5NetworkHeaderDetailsDialog.E5NetworkHeaderDetailsDialog?1(parent=None) eric6.E5Network.E5NetworkProxyFactory.E5NetworkProxyFactory.queryProxy?4(query)
--- a/eric6/APIs/Python3/eric6.bas Sun May 09 14:34:26 2021 +0200 +++ b/eric6/APIs/Python3/eric6.bas Sun May 09 14:53:27 2021 +0200 @@ -193,6 +193,7 @@ E5GoogleMailAuthBrowser QDialog E5GraphicsView QGraphicsView E5HorizontalToolBox E5TabWidget +E5JsonServer QTcpServer E5Led QWidget E5LedType enum.Enum E5LineEdit QLineEdit
--- a/eric6/Documentation/Help/source.qhp Sun May 09 14:34:26 2021 +0200 +++ b/eric6/Documentation/Help/source.qhp Sun May 09 14:53:27 2021 +0200 @@ -152,6 +152,8 @@ <section title="eric6.E5Network.E5Ftp" ref="eric6.E5Network.E5Ftp.html" /> <section title="eric6.E5Network.E5GoogleMail" ref="eric6.E5Network.E5GoogleMail.html" /> <section title="eric6.E5Network.E5GoogleMailHelpers" ref="eric6.E5Network.E5GoogleMailHelpers.html" /> + <section title="eric6.E5Network.E5JsonClient" ref="eric6.E5Network.E5JsonClient.html" /> + <section title="eric6.E5Network.E5JsonServer" ref="eric6.E5Network.E5JsonServer.html" /> <section title="eric6.E5Network.E5NetworkHeaderDetailsDialog" ref="eric6.E5Network.E5NetworkHeaderDetailsDialog.html" /> <section title="eric6.E5Network.E5NetworkProxyFactory" ref="eric6.E5Network.E5NetworkProxyFactory.html" /> <section title="eric6.E5Network.E5RFC6266" ref="eric6.E5Network.E5RFC6266.html" /> @@ -4165,6 +4167,25 @@ <keyword name="E5HorizontalToolBox.removeItem" id="E5HorizontalToolBox.removeItem" ref="eric6.E5Gui.E5ToolBox.html#E5HorizontalToolBox.removeItem" /> <keyword name="E5HorizontalToolBox.setItemEnabled" id="E5HorizontalToolBox.setItemEnabled" ref="eric6.E5Gui.E5ToolBox.html#E5HorizontalToolBox.setItemEnabled" /> <keyword name="E5HorizontalToolBox.setItemToolTip" id="E5HorizontalToolBox.setItemToolTip" ref="eric6.E5Gui.E5ToolBox.html#E5HorizontalToolBox.setItemToolTip" /> + <keyword name="E5JsonClient" id="E5JsonClient" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient" /> + <keyword name="E5JsonClient (Constructor)" id="E5JsonClient (Constructor)" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.__init__" /> + <keyword name="E5JsonClient (Module)" id="E5JsonClient (Module)" ref="eric6.E5Network.E5JsonClient.html" /> + <keyword name="E5JsonClient.__receiveJson" id="E5JsonClient.__receiveJson" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.__receiveJson" /> + <keyword name="E5JsonClient.handleCall" id="E5JsonClient.handleCall" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.handleCall" /> + <keyword name="E5JsonClient.run" id="E5JsonClient.run" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.run" /> + <keyword name="E5JsonClient.sendJson" id="E5JsonClient.sendJson" ref="eric6.E5Network.E5JsonClient.html#E5JsonClient.sendJson" /> + <keyword name="E5JsonServer" id="E5JsonServer" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer" /> + <keyword name="E5JsonServer (Constructor)" id="E5JsonServer (Constructor)" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.__init__" /> + <keyword name="E5JsonServer (Module)" id="E5JsonServer (Module)" ref="eric6.E5Network.E5JsonServer.html" /> + <keyword name="E5JsonServer.__handleDisconnect" id="E5JsonServer.__handleDisconnect" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.__handleDisconnect" /> + <keyword name="E5JsonServer.__receiveJson" id="E5JsonServer.__receiveJson" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.__receiveJson" /> + <keyword name="E5JsonServer.connectionNames" id="E5JsonServer.connectionNames" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.connectionNames" /> + <keyword name="E5JsonServer.handleCall" id="E5JsonServer.handleCall" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.handleCall" /> + <keyword name="E5JsonServer.handleNewConnection" id="E5JsonServer.handleNewConnection" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.handleNewConnection" /> + <keyword name="E5JsonServer.sendJson" id="E5JsonServer.sendJson" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.sendJson" /> + <keyword name="E5JsonServer.startClient" id="E5JsonServer.startClient" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.startClient" /> + <keyword name="E5JsonServer.stopAllClients" id="E5JsonServer.stopAllClients" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.stopAllClients" /> + <keyword name="E5JsonServer.stopClient" id="E5JsonServer.stopClient" ref="eric6.E5Network.E5JsonServer.html#E5JsonServer.stopClient" /> <keyword name="E5Led" id="E5Led" ref="eric6.E5Gui.E5Led.html#E5Led" /> <keyword name="E5Led (Constructor)" id="E5Led (Constructor)" ref="eric6.E5Gui.E5Led.html#E5Led.__init__" /> <keyword name="E5Led (Module)" id="E5Led (Module)" ref="eric6.E5Gui.E5Led.html" /> @@ -18732,6 +18753,8 @@ <file>eric6.E5Network.E5Ftp.html</file> <file>eric6.E5Network.E5GoogleMail.html</file> <file>eric6.E5Network.E5GoogleMailHelpers.html</file> + <file>eric6.E5Network.E5JsonClient.html</file> + <file>eric6.E5Network.E5JsonServer.html</file> <file>eric6.E5Network.E5NetworkHeaderDetailsDialog.html</file> <file>eric6.E5Network.E5NetworkProxyFactory.html</file> <file>eric6.E5Network.E5RFC6266.html</file>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/Documentation/Source/eric6.E5Network.E5JsonClient.html Sun May 09 14:53:27 2021 +0200 @@ -0,0 +1,191 @@ +<!DOCTYPE html> +<html><head> +<title>eric6.E5Network.E5JsonClient</title> +<meta charset="UTF-8"> +<style> +body { + background: #EDECE6; + margin: 0em 1em 10em 1em; + color: black; +} + +h1 { color: white; background: #85774A; } +h2 { color: white; background: #85774A; } +h3 { color: white; background: #9D936E; } +h4 { color: white; background: #9D936E; } + +a { color: #BA6D36; } + +</style> +</head> +<body> +<a NAME="top" ID="top"></a> +<h1>eric6.E5Network.E5JsonClient</h1> + +<p> +Module implementing the JSON based client base class. +</p> +<h3>Global Attributes</h3> + +<table> +<tr><td>None</td></tr> +</table> +<h3>Classes</h3> + +<table> + +<tr> +<td><a href="#E5JsonClient">E5JsonClient</a></td> +<td>Class implementing a JSON based client base class.</td> +</tr> +</table> +<h3>Functions</h3> + +<table> +<tr><td>None</td></tr> +</table> +<hr /> +<hr /> +<a NAME="E5JsonClient" ID="E5JsonClient"></a> +<h2>E5JsonClient</h2> + +<p> + Class implementing a JSON based client base class. +</p> +<h3>Derived from</h3> +None +<h3>Class Attributes</h3> + +<table> +<tr><td>None</td></tr> +</table> +<h3>Class Methods</h3> + +<table> +<tr><td>None</td></tr> +</table> +<h3>Methods</h3> + +<table> + +<tr> +<td><a href="#E5JsonClient.__init__">E5JsonClient</a></td> +<td>Constructor</td> +</tr> +<tr> +<td><a href="#E5JsonClient.__receiveJson">__receiveJson</a></td> +<td>Private method to receive a JSON encode command and data from the server.</td> +</tr> +<tr> +<td><a href="#E5JsonClient.handleCall">handleCall</a></td> +<td>Public method to handle a method call from the server.</td> +</tr> +<tr> +<td><a href="#E5JsonClient.run">run</a></td> +<td>Public method implementing the main loop of the client.</td> +</tr> +<tr> +<td><a href="#E5JsonClient.sendJson">sendJson</a></td> +<td>Public method to send a single refactoring command to the server.</td> +</tr> +</table> +<h3>Static Methods</h3> + +<table> +<tr><td>None</td></tr> +</table> + +<a NAME="E5JsonClient.__init__" ID="E5JsonClient.__init__"></a> +<h4>E5JsonClient (Constructor)</h4> +<b>E5JsonClient</b>(<i>host, port, idString=""</i>) + +<p> + Constructor +</p> +<dl> + +<dt><i>host</i> (str)</dt> +<dd> +ip address the background service is listening +</dd> +<dt><i>port</i> (int)</dt> +<dd> +port of the background service +</dd> +<dt><i>idString</i> (str)</dt> +<dd> +assigned client id to be sent back to the server in + order to identify the connection +</dd> +</dl> +<a NAME="E5JsonClient.__receiveJson" ID="E5JsonClient.__receiveJson"></a> +<h4>E5JsonClient.__receiveJson</h4> +<b>__receiveJson</b>(<i></i>) + +<p> + Private method to receive a JSON encode command and data from the + server. +</p> +<dl> +<dt>Return:</dt> +<dd> +tuple containing the received command and a dictionary + containing the associated data +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +tuple of (str, dict) +</dd> +</dl> +<a NAME="E5JsonClient.handleCall" ID="E5JsonClient.handleCall"></a> +<h4>E5JsonClient.handleCall</h4> +<b>handleCall</b>(<i>method, params</i>) + +<p> + Public method to handle a method call from the server. +</p> +<p> + Note: This is an empty implementation that must be overridden in + derived classes. +</p> +<dl> + +<dt><i>method</i> (str)</dt> +<dd> +requested method name +</dd> +<dt><i>params</i> (dict)</dt> +<dd> +dictionary with method specific parameters +</dd> +</dl> +<a NAME="E5JsonClient.run" ID="E5JsonClient.run"></a> +<h4>E5JsonClient.run</h4> +<b>run</b>(<i></i>) + +<p> + Public method implementing the main loop of the client. +</p> +<a NAME="E5JsonClient.sendJson" ID="E5JsonClient.sendJson"></a> +<h4>E5JsonClient.sendJson</h4> +<b>sendJson</b>(<i>command, params</i>) + +<p> + Public method to send a single refactoring command to the server. +</p> +<dl> + +<dt><i>command</i> (str)</dt> +<dd> +command name to be sent +</dd> +<dt><i>params</i> (dict)</dt> +<dd> +dictionary of named parameters for the command +</dd> +</dl> +<div align="right"><a href="#top">Up</a></div> +<hr /> +</body></html> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/Documentation/Source/eric6.E5Network.E5JsonServer.html Sun May 09 14:53:27 2021 +0200 @@ -0,0 +1,310 @@ +<!DOCTYPE html> +<html><head> +<title>eric6.E5Network.E5JsonServer</title> +<meta charset="UTF-8"> +<style> +body { + background: #EDECE6; + margin: 0em 1em 10em 1em; + color: black; +} + +h1 { color: white; background: #85774A; } +h2 { color: white; background: #85774A; } +h3 { color: white; background: #9D936E; } +h4 { color: white; background: #9D936E; } + +a { color: #BA6D36; } + +</style> +</head> +<body> +<a NAME="top" ID="top"></a> +<h1>eric6.E5Network.E5JsonServer</h1> + +<p> +Module implementing the JSON based server base class. +</p> +<h3>Global Attributes</h3> + +<table> +<tr><td>None</td></tr> +</table> +<h3>Classes</h3> + +<table> + +<tr> +<td><a href="#E5JsonServer">E5JsonServer</a></td> +<td>Class implementing a JSON based server base class.</td> +</tr> +</table> +<h3>Functions</h3> + +<table> +<tr><td>None</td></tr> +</table> +<hr /> +<hr /> +<a NAME="E5JsonServer" ID="E5JsonServer"></a> +<h2>E5JsonServer</h2> + +<p> + Class implementing a JSON based server base class. +</p> +<h3>Derived from</h3> +QTcpServer +<h3>Class Attributes</h3> + +<table> +<tr><td>None</td></tr> +</table> +<h3>Class Methods</h3> + +<table> +<tr><td>None</td></tr> +</table> +<h3>Methods</h3> + +<table> + +<tr> +<td><a href="#E5JsonServer.__init__">E5JsonServer</a></td> +<td>Constructor</td> +</tr> +<tr> +<td><a href="#E5JsonServer.__handleDisconnect">__handleDisconnect</a></td> +<td>Private slot handling a disconnect of the client.</td> +</tr> +<tr> +<td><a href="#E5JsonServer.__receiveJson">__receiveJson</a></td> +<td>Private slot handling received data from the client.</td> +</tr> +<tr> +<td><a href="#E5JsonServer.connectionNames">connectionNames</a></td> +<td>Public method to get the list of active connection names.</td> +</tr> +<tr> +<td><a href="#E5JsonServer.handleCall">handleCall</a></td> +<td>Public method to handle a method call from the client.</td> +</tr> +<tr> +<td><a href="#E5JsonServer.handleNewConnection">handleNewConnection</a></td> +<td>Public slot for new incoming connections from a client.</td> +</tr> +<tr> +<td><a href="#E5JsonServer.sendJson">sendJson</a></td> +<td>Public method to send a single command to a client.</td> +</tr> +<tr> +<td><a href="#E5JsonServer.startClient">startClient</a></td> +<td>Public method to start a client process.</td> +</tr> +<tr> +<td><a href="#E5JsonServer.stopAllClients">stopAllClients</a></td> +<td>Public method to stop all clients.</td> +</tr> +<tr> +<td><a href="#E5JsonServer.stopClient">stopClient</a></td> +<td>Public method to stop a client process.</td> +</tr> +</table> +<h3>Static Methods</h3> + +<table> +<tr><td>None</td></tr> +</table> + +<a NAME="E5JsonServer.__init__" ID="E5JsonServer.__init__"></a> +<h4>E5JsonServer (Constructor)</h4> +<b>E5JsonServer</b>(<i>name="", multiplex=False, parent=None</i>) + +<p> + Constructor +</p> +<dl> + +<dt><i>name</i> (str)</dt> +<dd> +name of the server (used for output only) +</dd> +<dt><i>multiplex</i> (bool)</dt> +<dd> +flag indicating a multiplexing server +</dd> +<dt><i>parent</i> (QObject)</dt> +<dd> +parent object +</dd> +</dl> +<a NAME="E5JsonServer.__handleDisconnect" ID="E5JsonServer.__handleDisconnect"></a> +<h4>E5JsonServer.__handleDisconnect</h4> +<b>__handleDisconnect</b>(<i>idString</i>) + +<p> + Private slot handling a disconnect of the client. +</p> +<dl> + +<dt><i>idString</i> (str)</dt> +<dd> +id of the connection been disconnected +</dd> +</dl> +<a NAME="E5JsonServer.__receiveJson" ID="E5JsonServer.__receiveJson"></a> +<h4>E5JsonServer.__receiveJson</h4> +<b>__receiveJson</b>(<i>idString</i>) + +<p> + Private slot handling received data from the client. +</p> +<dl> + +<dt><i>idString</i> (str)</dt> +<dd> +id of the connection been disconnected +</dd> +</dl> +<a NAME="E5JsonServer.connectionNames" ID="E5JsonServer.connectionNames"></a> +<h4>E5JsonServer.connectionNames</h4> +<b>connectionNames</b>(<i></i>) + +<p> + Public method to get the list of active connection names. +</p> +<p> + If this is not a multiplexing server, an empty list is returned. +</p> +<dl> +<dt>Return:</dt> +<dd> +list of active connection names +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +list of str +</dd> +</dl> +<a NAME="E5JsonServer.handleCall" ID="E5JsonServer.handleCall"></a> +<h4>E5JsonServer.handleCall</h4> +<b>handleCall</b>(<i>method, params</i>) + +<p> + Public method to handle a method call from the client. +</p> +<p> + Note: This is an empty implementation that must be overridden in + derived classes. +</p> +<dl> + +<dt><i>method</i> (str)</dt> +<dd> +requested method name +</dd> +<dt><i>params</i> (dict)</dt> +<dd> +dictionary with method specific parameters +</dd> +</dl> +<a NAME="E5JsonServer.handleNewConnection" ID="E5JsonServer.handleNewConnection"></a> +<h4>E5JsonServer.handleNewConnection</h4> +<b>handleNewConnection</b>(<i></i>) + +<p> + Public slot for new incoming connections from a client. +</p> +<a NAME="E5JsonServer.sendJson" ID="E5JsonServer.sendJson"></a> +<h4>E5JsonServer.sendJson</h4> +<b>sendJson</b>(<i>command, params, flush=False, idString=""</i>) + +<p> + Public method to send a single command to a client. +</p> +<dl> + +<dt><i>command</i> (str)</dt> +<dd> +command name to be sent +</dd> +<dt><i>params</i> (dict)</dt> +<dd> +dictionary of named parameters for the command +</dd> +<dt><i>flush</i> (bool)</dt> +<dd> +flag indicating to flush the data to the socket +</dd> +<dt><i>idString</i> (str)</dt> +<dd> +id of the connection to send data to +</dd> +</dl> +<a NAME="E5JsonServer.startClient" ID="E5JsonServer.startClient"></a> +<h4>E5JsonServer.startClient</h4> +<b>startClient</b>(<i>interpreter, clientScript, clientArgs, idString="", environment=None</i>) + +<p> + Public method to start a client process. +</p> +<dl> + +<dt><i>interpreter</i> (str)</dt> +<dd> +interpreter to be used for the client +</dd> +<dt><i>clientScript</i> (str)</dt> +<dd> +path to the client script +</dd> +<dt><i>clientArgs</i></dt> +<dd> +list of arguments for the client +</dd> +<dt><i>idString</i> (str)</dt> +<dd> +id of the client to be started +</dd> +<dt><i>environment</i> (dict)</dt> +<dd> +dictionary of environment settings to pass +</dd> +</dl> +<dl> +<dt>Return:</dt> +<dd> +flag indicating a successful client start +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +bool +</dd> +</dl> +<a NAME="E5JsonServer.stopAllClients" ID="E5JsonServer.stopAllClients"></a> +<h4>E5JsonServer.stopAllClients</h4> +<b>stopAllClients</b>(<i></i>) + +<p> + Public method to stop all clients. +</p> +<a NAME="E5JsonServer.stopClient" ID="E5JsonServer.stopClient"></a> +<h4>E5JsonServer.stopClient</h4> +<b>stopClient</b>(<i>idString=""</i>) + +<p> + Public method to stop a client process. +</p> +<dl> + +<dt><i>idString</i> (str)</dt> +<dd> +id of the client to be stopped +</dd> +</dl> +<div align="right"><a href="#top">Up</a></div> +<hr /> +</body></html> \ No newline at end of file
--- a/eric6/Documentation/Source/index-eric6.E5Network.html Sun May 09 14:34:26 2021 +0200 +++ b/eric6/Documentation/Source/index-eric6.E5Network.html Sun May 09 14:53:27 2021 +0200 @@ -50,6 +50,14 @@ <td>Module implementing some helpers for Google mail.</td> </tr> <tr> +<td><a href="eric6.E5Network.E5JsonClient.html">E5JsonClient</a></td> +<td>Module implementing the JSON based client base class.</td> +</tr> +<tr> +<td><a href="eric6.E5Network.E5JsonServer.html">E5JsonServer</a></td> +<td>Module implementing the JSON based server base class.</td> +</tr> +<tr> <td><a href="eric6.E5Network.E5NetworkHeaderDetailsDialog.html">E5NetworkHeaderDetailsDialog</a></td> <td>Module implementing a dialog to show the data of a response or reply header.</td> </tr>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/E5Network/E5JsonClient.py Sun May 09 14:53:27 2021 +0200 @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the JSON based client base class. +""" + +import io +import sys +import socket +import select +import traceback +import json +import contextlib + + +class E5JsonClient: + """ + Class implementing a JSON based client base class. + """ + def __init__(self, host, port, idString=""): + """ + Constructor + + @param host ip address the background service is listening + @type str + @param port port of the background service + @type int + @param idString assigned client id to be sent back to the server in + order to identify the connection + @type str + """ + self.__connection = socket.create_connection((host, port)) + if idString: + reply = idString + '\n' + self.__connection.sendall(reply.encode('utf8', 'backslashreplace')) + + def sendJson(self, command, params): + """ + Public method to send a single refactoring command to the server. + + @param command command name to be sent + @type str + @param params dictionary of named parameters for the command + @type dict + """ + commandDict = { + "jsonrpc": "2.0", + "method": command, + "params": params, + } + cmd = json.dumps(commandDict) + '\n' + self.__connection.sendall(cmd.encode('utf8', 'backslashreplace')) + + def __receiveJson(self): + """ + Private method to receive a JSON encode command and data from the + server. + + @return tuple containing the received command and a dictionary + containing the associated data + @rtype tuple of (str, dict) + """ + # step 1: receive the data + # The JSON RPC string is prefixed by a 9 character long length field. + length = self.__connection.recv(9) + if len(length) < 9: + # invalid length string received + return None, None + + length = int(length) + data = b'' + while len(data) < length: + newData = self.__connection.recv(length - len(data)) + if not newData: + return None, None + + data += newData + + # step 2: decode and convert the data + line = data.decode( + 'utf8', 'backslashreplace') + try: + commandDict = json.loads(line.strip()) + except (TypeError, ValueError) as err: + self.sendJson("ClientException", { + "ExceptionType": "ProtocolError", + "ExceptionValue": str(err), + "ProtocolData": line.strip(), + }) + return None, None + + method = commandDict["method"] + params = commandDict["params"] + + return method, params + + def handleCall(self, method, params): + """ + Public method to handle a method call from the server. + + Note: This is an empty implementation that must be overridden in + derived classes. + + @param method requested method name + @type str + @param params dictionary with method specific parameters + @type dict + """ + pass + + def run(self): + """ + Public method implementing the main loop of the client. + """ + try: + selectErrors = 0 + while selectErrors <= 10: # selected arbitrarily + try: + rrdy, wrdy, xrdy = select.select( + [self.__connection], [], []) + + # Just waiting for self.__connection. Therefor no check + # needed. + method, params = self.__receiveJson() + if method is None: + selectErrors += 1 + elif method == "Exit": + break + else: + self.handleCall(method, params) + + # reset select errors + selectErrors = 0 + + except (select.error, KeyboardInterrupt, socket.error): + selectErrors += 1 + + except Exception: + exctype, excval, exctb = sys.exc_info() + tbinfofile = io.StringIO() + traceback.print_tb(exctb, None, tbinfofile) + tbinfofile.seek(0) + tbinfo = tbinfofile.read() + del exctb + self.sendJson("ClientException", { + "ExceptionType": str(exctype), + "ExceptionValue": str(excval), + "Traceback": tbinfo, + }) + + # Give time to process latest response on server side + with contextlib.suppress(socket.error, OSError): + self.__connection.shutdown(socket.SHUT_RDWR) + self.__connection.close()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/E5Network/E5JsonServer.py Sun May 09 14:53:27 2021 +0200 @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the JSON based server base class. +""" + +import contextlib +import json + +from PyQt5.QtCore import ( + pyqtSlot, QProcess, QProcessEnvironment, QCoreApplication, QEventLoop, + QTimer +) +from PyQt5.QtNetwork import QTcpServer, QHostAddress + +from E5Gui import E5MessageBox + +import Preferences +import Utilities + + +class E5JsonServer(QTcpServer): + """ + Class implementing a JSON based server base class. + """ + def __init__(self, name="", multiplex=False, parent=None): + """ + Constructor + + @param name name of the server (used for output only) + @type str + @param multiplex flag indicating a multiplexing server + @type bool + @param parent parent object + @type QObject + """ + super().__init__(parent) + + self.__name = name + self.__multiplex = multiplex + if self.__multiplex: + self.__clientProcesses = {} + self.__connections = {} + else: + self.__clientProcess = None + self.__connection = None + + # setup the network interface + networkInterface = Preferences.getDebugger("NetworkInterface") + if networkInterface == "all" or '.' in networkInterface: + # IPv4 + self.__hostAddress = '127.0.0.1' + else: + # IPv6 + self.__hostAddress = '::1' + self.listen(QHostAddress(self.__hostAddress)) + + self.newConnection.connect(self.handleNewConnection) + + port = self.serverPort() + ## Note: Need the port if client is started external in debugger. + print('JSON server ({1}) listening on: {0:d}' # __IGNORE_WARNING__ + .format(port, self.__name)) + + @pyqtSlot() + def handleNewConnection(self): + """ + Public slot for new incoming connections from a client. + """ + connection = self.nextPendingConnection() + if not connection.isValid(): + return + + if self.__multiplex: + if not connection.waitForReadyRead(3000): + return + idString = bytes(connection.readLine()).decode( + "utf-8", 'backslashreplace').strip() + if idString in self.__connections: + self.__connections[idString].close() + self.__connections[idString] = connection + else: + idString = "" + if self.__connection is not None: + self.__connection.close() + + self.__connection = connection + + connection.readyRead.connect( + lambda: self.__receiveJson(idString)) + connection.disconnected.connect( + lambda: self.__handleDisconnect(idString)) + + @pyqtSlot() + def __handleDisconnect(self, idString): + """ + Private slot handling a disconnect of the client. + + @param idString id of the connection been disconnected + @type str + """ + if idString: + if idString in self.__connections: + self.__connections[idString].close() + del self.__connections[idString] + else: + if self.__connection is not None: + self.__connection.close() + + self.__connection = None + + def connectionNames(self): + """ + Public method to get the list of active connection names. + + If this is not a multiplexing server, an empty list is returned. + + @return list of active connection names + @rtype list of str + """ + if self.__multiplex: + return list(self.__connections.keys()) + else: + return [] + + @pyqtSlot() + def __receiveJson(self, idString): + """ + Private slot handling received data from the client. + + @param idString id of the connection been disconnected + @type str + """ + if idString: + try: + connection = self.__connections[idString] + except KeyError: + connection = None + else: + connection = self.__connection + + while connection and connection.canReadLine(): + data = connection.readLine() + jsonLine = bytes(data).decode("utf-8", 'backslashreplace') + + #- print("JSON Server ({0}): {1}".format(self.__name, jsonLine)) + #- this is for debugging only + + try: + clientDict = json.loads(jsonLine.strip()) + except (TypeError, ValueError) as err: + E5MessageBox.critical( + None, + self.tr("JSON Protocol Error"), + self.tr("""<p>The response received from the client""" + """ could not be decoded. Please report""" + """ this issue with the received data to the""" + """ eric bugs email address.</p>""" + """<p>Error: {0}</p>""" + """<p>Data:<br/>{1}</p>""").format( + str(err), Utilities.html_encode(jsonLine.strip())), + E5MessageBox.StandardButtons( + E5MessageBox.Ok)) + return + + self.handleCall(clientDict["method"], clientDict["params"]) + + def sendJson(self, command, params, flush=False, idString=""): + """ + Public method to send a single command to a client. + + @param command command name to be sent + @type str + @param params dictionary of named parameters for the command + @type dict + @param flush flag indicating to flush the data to the socket + @type bool + @param idString id of the connection to send data to + @type str + """ + commandDict = { + "jsonrpc": "2.0", + "method": command, + "params": params, + } + cmd = json.dumps(commandDict) + '\n' + + if idString: + try: + connection = self.__connections[idString] + except KeyError: + connection = None + else: + connection = self.__connection + + if connection is not None: + data = cmd.encode('utf8', 'backslashreplace') + length = "{0:09d}".format(len(data)) + connection.write(length.encode() + data) + if flush: + connection.flush() + + def startClient(self, interpreter, clientScript, clientArgs, idString="", + environment=None): + """ + Public method to start a client process. + + @param interpreter interpreter to be used for the client + @type str + @param clientScript path to the client script + @type str + @param clientArgs list of arguments for the client + @param idString id of the client to be started + @type str + @param environment dictionary of environment settings to pass + @type dict + @return flag indicating a successful client start + @rtype bool + """ + if interpreter == "" or not Utilities.isinpath(interpreter): + return False + + proc = QProcess() + proc.setProcessChannelMode( + QProcess.ProcessChannelMode.ForwardedChannels) + if environment is not None: + env = QProcessEnvironment() + for key, value in list(environment.items()): + env.insert(key, value) + proc.setProcessEnvironment(env) + args = [clientScript, self.__hostAddress, str(self.serverPort())] + if idString: + args.append(idString) + args.extend(clientArgs) + proc.start(interpreter, args) + if not proc.waitForStarted(10000): + proc = None + + if idString: + self.__clientProcesses[idString] = proc + if proc: + timer = QTimer() + timer.setSingleShot(True) + timer.start(30000) # 30s timeout + while ( + idString not in self.connectionNames() and + timer.isActive() + ): + # Give the event loop the chance to process the new + # connection of the client (= slow start). + QCoreApplication.processEvents( + QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents) + else: + self.__clientProcess = proc + + return proc is not None + + def stopClient(self, idString=""): + """ + Public method to stop a client process. + + @param idString id of the client to be stopped + @type str + """ + self.sendJson("Exit", {}, flush=True, idString=idString) + + if idString: + try: + connection = self.__connections[idString] + except KeyError: + connection = None + else: + connection = self.__connection + if connection is not None: + connection.waitForDisconnected() + + if idString: + with contextlib.suppress(KeyError): + self .__clientProcesses[idString].close() + del self.__clientProcesses[idString] + else: + if self.__clientProcess is not None: + self.__clientProcess.close() + self.__clientProcess = None + + def stopAllClients(self): + """ + Public method to stop all clients. + """ + clientNames = self.connectionNames()[:] + for clientName in clientNames: + self.stopClient(clientName) + + ####################################################################### + ## The following methods should be overridden by derived classes + ####################################################################### + + def handleCall(self, method, params): + """ + Public method to handle a method call from the client. + + Note: This is an empty implementation that must be overridden in + derived classes. + + @param method requested method name + @type str + @param params dictionary with method specific parameters + @type dict + """ + pass