src/eric7/EricNetwork/EricJsonClient.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9057
ddc46e93ccc4
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2017 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the JSON based client base class.
8 """
9
10 import io
11 import sys
12 import socket
13 import select
14 import traceback
15 import json
16 import contextlib
17
18
19 class EricJsonClient:
20 """
21 Class implementing a JSON based client base class.
22 """
23 def __init__(self, host, port, idString=""):
24 """
25 Constructor
26
27 @param host IP address the background service is listening
28 @type str
29 @param port port of the background service
30 @type int
31 @param idString assigned client id to be sent back to the server in
32 order to identify the connection
33 @type str
34 """
35 self.__connection = socket.create_connection((host, port))
36 if idString:
37 reply = idString + '\n'
38 self.__connection.sendall(reply.encode('utf8', 'backslashreplace'))
39
40 def sendJson(self, command, params):
41 """
42 Public method to send a single refactoring command to the server.
43
44 @param command command name to be sent
45 @type str
46 @param params dictionary of named parameters for the command
47 @type dict
48 """
49 commandDict = {
50 "jsonrpc": "2.0",
51 "method": command,
52 "params": params,
53 }
54 cmd = json.dumps(commandDict) + '\n'
55 self.__connection.sendall(cmd.encode('utf8', 'backslashreplace'))
56
57 def __receiveJson(self):
58 """
59 Private method to receive a JSON encode command and data from the
60 server.
61
62 @return tuple containing the received command and a dictionary
63 containing the associated data
64 @rtype tuple of (str, dict)
65 """
66 # step 1: receive the data
67 # The JSON RPC string is prefixed by a 9 character long length field.
68 length = self.__connection.recv(9)
69 if len(length) < 9:
70 # invalid length string received
71 return None, None
72
73 length = int(length)
74 data = b''
75 while len(data) < length:
76 newData = self.__connection.recv(length - len(data))
77 if not newData:
78 return None, None
79
80 data += newData
81
82 # step 2: decode and convert the data
83 line = data.decode(
84 'utf8', 'backslashreplace')
85 try:
86 commandDict = json.loads(line.strip())
87 except (TypeError, ValueError) as err:
88 self.sendJson("ClientException", {
89 "ExceptionType": "ProtocolError",
90 "ExceptionValue": str(err),
91 "ProtocolData": line.strip(),
92 })
93 return None, None
94
95 method = commandDict["method"]
96 params = commandDict["params"]
97
98 return method, params
99
100 def handleCall(self, method, params):
101 """
102 Public method to handle a method call from the server.
103
104 Note: This is an empty implementation that must be overridden in
105 derived classes.
106
107 @param method requested method name
108 @type str
109 @param params dictionary with method specific parameters
110 @type dict
111 """
112 pass
113
114 def run(self):
115 """
116 Public method implementing the main loop of the client.
117 """
118 try:
119 selectErrors = 0
120 while selectErrors <= 10: # selected arbitrarily
121 try:
122 rrdy, wrdy, xrdy = select.select(
123 [self.__connection], [], [])
124
125 # Just waiting for self.__connection. Therefor no check
126 # needed.
127 method, params = self.__receiveJson()
128 if method is None:
129 selectErrors += 1
130 elif method == "Exit":
131 break
132 else:
133 self.handleCall(method, params)
134
135 # reset select errors
136 selectErrors = 0
137
138 except (select.error, KeyboardInterrupt, socket.error):
139 selectErrors += 1
140
141 except Exception:
142 exctype, excval, exctb = sys.exc_info()
143 tbinfofile = io.StringIO()
144 traceback.print_tb(exctb, None, tbinfofile)
145 tbinfofile.seek(0)
146 tbinfo = tbinfofile.read()
147 del exctb
148 self.sendJson("ClientException", {
149 "ExceptionType": str(exctype),
150 "ExceptionValue": str(excval),
151 "Traceback": tbinfo,
152 })
153
154 # Give time to process latest response on server side
155 with contextlib.suppress(socket.error, OSError):
156 self.__connection.shutdown(socket.SHUT_RDWR)
157 self.__connection.close()
158
159 def poll(self, waitMethod=""):
160 """
161 Public method to check and receive one message (if available).
162
163 @param waitMethod name of a method to wait for
164 @type str
165 @return dictionary containing the data of the waited for method
166 @rtype dict
167 """
168 try:
169 if waitMethod:
170 rrdy, wrdy, xrdy = select.select(
171 [self.__connection], [], [])
172 else:
173 rrdy, wrdy, xrdy = select.select(
174 [self.__connection], [], [], 0)
175
176 if self.__connection in rrdy:
177 method, params = self.__receiveJson()
178 if method is not None:
179 if method == "Exit":
180 self.__exitClient = True
181 elif method == waitMethod:
182 return params
183 else:
184 self.handleCall(method, params)
185
186 except (select.error, KeyboardInterrupt, socket.error):
187 # just ignore these
188 pass
189
190 except Exception:
191 exctype, excval, exctb = sys.exc_info()
192 tbinfofile = io.StringIO()
193 traceback.print_tb(exctb, None, tbinfofile)
194 tbinfofile.seek(0)
195 tbinfo = tbinfofile.read()
196 del exctb
197 self.sendJson("ClientException", {
198 "ExceptionType": str(exctype),
199 "ExceptionValue": str(excval),
200 "Traceback": tbinfo,
201 })
202
203 return None

eric ide

mercurial