43 # handler(request:str, params:dict, reqestUuid:str) -> None |
46 # handler(request:str, params:dict, reqestUuid:str) -> None |
44 self.__registerInternalHandlers() |
47 self.__registerInternalHandlers() |
45 |
48 |
46 self.__connection = None |
49 self.__connection = None |
47 |
50 |
|
51 self.__selector = selectors.DefaultSelector() |
|
52 |
|
53 # create and register the 'Debugger' request handler |
|
54 self.__debuggerRequestHandler = EricServerDebuggerRequestHandler(self) |
|
55 self.registerRequestHandler( |
|
56 EricRequestCategory.Debugger, |
|
57 self.__debuggerRequestHandler.handleRequest, |
|
58 ) |
|
59 |
|
60 # create and register the 'File System' request handler |
|
61 self.__fileSystemRequestHandler = EricServerFileSystemRequestHandler(self) |
|
62 self.registerRequestHandler( |
|
63 EricRequestCategory.FileSystem, |
|
64 self.__fileSystemRequestHandler.handleRequest, |
|
65 ) |
|
66 |
|
67 # TODO: 'Project' handler not implemented yet |
|
68 # TODO: implement an 'EditorConfig' handler (?) |
|
69 |
48 address = ("", port) |
70 address = ("", port) |
49 if socket.has_dualstack_ipv6() and useIPv6: |
71 if socket.has_dualstack_ipv6() and useIPv6: |
50 self.__socket = socket.create_server( |
72 self.__socket = socket.create_server( |
51 address, family=socket.AF_INET6, dualstack_ipv6=True |
73 address, family=socket.AF_INET6, dualstack_ipv6=True |
52 ) |
74 ) |
53 else: |
75 else: |
54 self.__socket = socket.create_server( |
76 self.__socket = socket.create_server(address, family=socket.AF_INET) |
55 address, family=socket.AF_INET |
77 |
56 ) |
78 def getSelector(self): |
|
79 """ |
|
80 Public method to get a reference to the selector object. |
|
81 """ |
|
82 return self.__selector |
57 |
83 |
58 ####################################################################### |
84 ####################################################################### |
59 ## Methods for receiving requests and sending the results. |
85 ## Methods for receiving requests and sending the results. |
60 ####################################################################### |
86 ####################################################################### |
61 |
87 |
71 @type dict |
97 @type dict |
72 @param reqestUuid UUID of the associated request as sent by the eric IDE |
98 @param reqestUuid UUID of the associated request as sent by the eric IDE |
73 (defaults to "", i.e. no UUID received) |
99 (defaults to "", i.e. no UUID received) |
74 @type str |
100 @type str |
75 """ |
101 """ |
76 commandDict = { |
102 if self.__connection is not None: |
77 "jsonrpc": "2.0", |
103 commandDict = { |
78 "category": category, |
104 "jsonrpc": "2.0", |
79 "reply": reply, |
105 "category": category, |
80 "params": params, |
106 "reply": reply, |
81 "uuid": reqestUuid, |
107 "params": params, |
82 } |
108 "uuid": reqestUuid, |
83 data = json.dumps(commandDict).encode("utf8", "backslashreplace") |
109 } |
|
110 self.sendJsonCommand(commandDict, self.__connection) |
|
111 |
|
112 def sendJsonCommand(self, jsonCommand, sock): |
|
113 """ |
|
114 Public method to send a JSON encoded command/response via a given socket. |
|
115 |
|
116 @param jsonCommand dictionary containing the command data or a JSON encoded |
|
117 command string |
|
118 @type dict or str |
|
119 @param sock reference to the socket to send the data to |
|
120 @type socket.socket |
|
121 """ |
|
122 if isinstance(jsonCommand, dict): |
|
123 jsonCommand = json.dumps(jsonCommand) |
|
124 data = jsonCommand.encode("utf8", "backslashreplace") |
84 header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF) |
125 header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF) |
85 self.__connection.sendall(header) |
126 sock.sendall(header) |
86 self.__connection.sendall(data) |
127 sock.sendall(data) |
87 |
128 |
88 def __receiveBytes(self, length): |
129 def __receiveBytes(self, length, sock): |
89 """ |
130 """ |
90 Private method to receive the given length of bytes. |
131 Private method to receive the given length of bytes. |
91 |
132 |
92 @param length bytes to receive |
133 @param length bytes to receive |
93 @type int |
134 @type int |
|
135 @param sock reference to the socket to receive the data from |
|
136 @type socket.socket |
94 @return received bytes or None if connection closed |
137 @return received bytes or None if connection closed |
95 @rtype bytes |
138 @rtype bytes |
96 """ |
139 """ |
97 data = bytearray() |
140 data = bytearray() |
98 while len(data) < length: |
141 while len(data) < length: |
99 newData = self.__connection.recv(length - len(data)) |
142 try: |
100 if not newData: |
143 newData = sock.recv(length - len(data)) |
101 return None |
144 if not newData: |
102 |
145 print(str(newData)) |
103 data += newData |
146 return None |
|
147 |
|
148 data += newData |
|
149 except OSError as err: |
|
150 if err.errno != 11: |
|
151 data = None # in case some data was received already |
|
152 break |
104 return data |
153 return data |
105 |
154 |
106 def __receiveJson(self): |
155 def receiveJsonCommand(self, sock): |
107 """ |
156 """ |
108 Private method to receive a JSON encoded command and data from the |
157 Public method to receive a JSON encoded command and data. |
109 server. |
158 |
110 |
159 @param sock reference to the socket to receive the data from |
111 @return tuple containing the received service category, the command, |
160 @type socket.socket |
112 a dictionary containing the associated data and the UUID of the |
161 @return dictionary containing the JSON command data or None to signal |
113 request |
162 an issue while receiving data |
114 @rtype tuple of (int, str, dict, str) |
163 @rtype dict |
115 """ |
164 """ |
116 # step 1: receive the data |
165 if self.isSocketClosed(sock): |
117 header = self.__receiveBytes(struct.calcsize(b"!II")) |
166 return None |
|
167 |
|
168 header = self.__receiveBytes(struct.calcsize(b"!II"), sock) |
118 if not header: |
169 if not header: |
119 return EricRequestCategory.Error, None, None, None |
170 return {} |
120 |
171 |
121 length, datahash = struct.unpack(b"!II", header) |
172 length, datahash = struct.unpack(b"!II", header) |
122 |
173 |
123 length = int(length) |
174 length = int(length) |
124 data = self.__receiveBytes(length) |
175 data = self.__receiveBytes(length, sock) |
|
176 if data is None: |
|
177 return None |
|
178 |
125 if not data or zlib.adler32(data) & 0xFFFFFFFF != datahash: |
179 if not data or zlib.adler32(data) & 0xFFFFFFFF != datahash: |
126 self.sendJson( |
180 self.sendJson( |
127 category=EricRequestCategory.Error, |
181 category=EricRequestCategory.Error, |
128 reply="ClientChecksumException", |
182 reply="EricServerChecksumException", |
129 params={ |
183 params={ |
130 "ExceptionType": "ProtocolChecksumError", |
184 "ExceptionType": "ProtocolChecksumError", |
131 "ExceptionValue": "The checksum of the data does not match.", |
185 "ExceptionValue": "The checksum of the data does not match.", |
132 "ProtocolData": data.decode("utf8", "backslashreplace"), |
186 "ProtocolData": data.decode("utf8", "backslashreplace"), |
133 }, |
187 }, |
134 ) |
188 ) |
135 return EricRequestCategory.Error, None, None, None |
189 return {} |
136 |
190 |
137 # step 2: decode and convert the data |
191 jsonStr = data.decode("utf8", "backslashreplace") |
138 jsonString = data.decode("utf8", "backslashreplace") |
|
139 try: |
192 try: |
140 requestDict = json.loads(jsonString.strip()) |
193 return json.loads(jsonStr.strip()) |
141 except (TypeError, ValueError) as err: |
194 except (TypeError, ValueError) as err: |
142 self.sendJson( |
195 self.sendJson( |
143 category=EricRequestCategory.Error, |
196 category=EricRequestCategory.Error, |
144 reply="ClientException", |
197 reply="EricServerException", |
145 params={ |
198 params={ |
146 "ExceptionType": "ProtocolError", |
199 "ExceptionType": "ProtocolError", |
147 "ExceptionValue": str(err), |
200 "ExceptionValue": str(err), |
148 "ProtocolData": jsonString.strip(), |
201 "ProtocolData": jsonStr.strip(), |
149 }, |
202 }, |
150 ) |
203 ) |
|
204 return {} |
|
205 |
|
206 def __receiveJson(self): |
|
207 """ |
|
208 Private method to receive a JSON encoded command and data from the |
|
209 server. |
|
210 |
|
211 @return tuple containing the received service category, the command, |
|
212 a dictionary containing the associated data and the UUID of the |
|
213 request |
|
214 @rtype tuple of (int, str, dict, str) |
|
215 """ |
|
216 requestDict = self.receiveJsonCommand(self.__connection) |
|
217 |
|
218 if not requestDict: |
151 return EricRequestCategory.Error, None, None, None |
219 return EricRequestCategory.Error, None, None, None |
152 |
220 |
153 category = requestDict["category"] |
221 category = requestDict["category"] |
154 request = requestDict["request"] |
222 request = requestDict["request"] |
155 params = requestDict["params"] |
223 params = requestDict["params"] |
156 reqestUuid = requestDict["uuid"] |
224 reqestUuid = requestDict["uuid"] |
157 |
225 |
158 return category, request, params, reqestUuid |
226 return category, request, params, reqestUuid |
159 |
227 |
|
228 def isSocketClosed(self, sock): |
|
229 """ |
|
230 Public method to check, if a given socket is closed. |
|
231 |
|
232 @param sock reference to the socket to be checked |
|
233 @type socket.socket |
|
234 @return flag indicating a closed state |
|
235 @rtype bool |
|
236 """ |
|
237 try: |
|
238 # this will try to read bytes without removing them from buffer (peek only) |
|
239 data = sock.recv(16, socket.MSG_PEEK) |
|
240 if len(data) == 0: |
|
241 return True |
|
242 except BlockingIOError: |
|
243 return False # socket is open and reading from it would block |
|
244 except ConnectionError: |
|
245 return True # socket was closed for some other reason |
|
246 except Exception: |
|
247 return False |
|
248 return False |
|
249 |
160 ####################################################################### |
250 ####################################################################### |
161 ## Methods for the server main loop. |
251 ## Methods for the server main loop. |
162 ####################################################################### |
252 ####################################################################### |
163 |
253 |
164 def __shutdown(self): |
254 def __shutdown(self): |
165 """ |
255 """ |
166 Private method to shut down the server. |
256 Private method to shut down the server. |
167 """ |
257 """ |
|
258 self.__closeIdeConnection() |
|
259 |
|
260 print("Stop listening for 'eric-ide' connections.") |
168 self.__socket.shutdown(socket.SHUT_RDWR) |
261 self.__socket.shutdown(socket.SHUT_RDWR) |
169 self.__socket.close() |
262 self.__socket.close() |
|
263 |
|
264 self.__selector.close() |
|
265 |
|
266 def __acceptIdeConnection(self, sock): |
|
267 """ |
|
268 Private method to accept the connection on the listening IDE server socket. |
|
269 |
|
270 @param sock reference to the listening socket |
|
271 @type socket.socket |
|
272 """ |
|
273 self.__connection, address = sock.accept() # Should be ready to read |
|
274 print(f"'eric-ide' connection from {address[0]}, port {address[1]}") |
|
275 self.__connection.setblocking(False) |
|
276 data = types.SimpleNamespace( |
|
277 name="eric-ide", address=address, handler=self.__serviceIdeConnection |
|
278 ) |
|
279 events = selectors.EVENT_READ |
|
280 self.__selector.register(self.__connection, events, data=data) |
|
281 |
|
282 def __closeIdeConnection(self): |
|
283 """ |
|
284 Private method to close the connection to an eric-ide. |
|
285 """ |
|
286 if self.__connection is not None: |
|
287 print( |
|
288 f"Closing 'eric-ide' connection to {self.__connection.getpeername()}." |
|
289 ) |
|
290 self.__selector.unregister(self.__connection) |
|
291 self.__connection.shutdown(socket.SHUT_RDWR) |
|
292 self.__connection.close() |
|
293 self.__connection = None |
|
294 |
|
295 self.__debuggerRequestHandler.shutdownClients() |
|
296 |
|
297 def __serviceIdeConnection(self, key): |
|
298 """ |
|
299 Private method to service the eric-ide connection. |
|
300 |
|
301 @param key reference to the SelectorKey object associated with the connection |
|
302 to be serviced |
|
303 @type selectors.SelectorKey |
|
304 """ |
|
305 if key.data.name == "eric-ide": |
|
306 category, request, params, reqestUuid = self.__receiveJson() |
|
307 if category == EricRequestCategory.Error or request is None: |
|
308 self.__closeIdeConnection() |
|
309 return |
|
310 |
|
311 if category == EricRequestCategory.Server: |
|
312 if request.lower() == "shutdown": |
|
313 self.__shouldStop = True |
|
314 return |
|
315 |
|
316 self.__handleRequest(category, request, params, reqestUuid) |
170 |
317 |
171 def run(self): |
318 def run(self): |
172 """ |
319 """ |
173 Public method implementing the remote server main loop. |
320 Public method implementing the remote server main loop. |
174 |
321 |
180 </ul> |
327 </ul> |
181 |
328 |
182 @return flag indicating a clean shutdown |
329 @return flag indicating a clean shutdown |
183 @rtype bool |
330 @rtype bool |
184 """ |
331 """ |
185 shutdown = False |
|
186 cleanExit = True |
332 cleanExit = True |
|
333 self.__shouldStop = False |
187 |
334 |
188 # listen on the server socket for new connections |
335 # listen on the server socket for new connections |
189 self.__socket.listen(1) |
336 self.__socket.listen(1) |
|
337 self.__socket.setblocking(False) |
|
338 print(f"Listening for 'eric-ide' connections on {self.__socket.getsockname()}") |
|
339 data = types.SimpleNamespace( |
|
340 name="server", acceptHandler=self.__acceptIdeConnection |
|
341 ) |
|
342 self.__selector.register(self.__socket, selectors.EVENT_READ, data=data) |
|
343 |
|
344 self.__debuggerRequestHandler.initServerSocket() |
190 |
345 |
191 while True: |
346 while True: |
192 try: |
347 try: |
193 # accept the next pending connection |
348 events = self.__selector.select(timeout=None) |
194 print("Waiting for connection...") |
349 for key, mask in events: |
195 self.__connection, address = self.__socket.accept() |
350 if key.data.name == "server": |
196 print(f"Connection from {address[0]}, port {address[1]}") |
351 # it is an event for a server socket |
197 |
352 key.data.acceptHandler(key.fileobj) |
198 selectErrors = 0 |
353 else: |
199 while selectErrors <= 10: # selected arbitrarily |
354 key.data.handler(key) |
200 try: |
|
201 rrdy, wrdy, xrdy = select.select([self.__connection], [], []) |
|
202 |
|
203 # Just waiting for self.__connection. Therefore no check |
|
204 # needed. |
|
205 category, request, params, reqestUuid = self.__receiveJson() |
|
206 if category == EricRequestCategory.Error or request is None: |
|
207 selectErrors += 1 |
|
208 elif category == EricRequestCategory.Server: |
|
209 if request.lower() == "exit": |
|
210 break |
|
211 elif request.lower() == "shutdown": |
|
212 shutdown = True |
|
213 break |
|
214 else: |
|
215 self.__handleRequest( |
|
216 category, request, params, reqestUuid |
|
217 ) |
|
218 else: |
|
219 self.__handleRequest(category, request, params, reqestUuid) |
|
220 |
|
221 # reset select errors |
|
222 selectErrors = 0 |
|
223 |
|
224 except (select.error, socket.error): |
|
225 selectErrors += 1 |
|
226 |
355 |
227 except KeyboardInterrupt: |
356 except KeyboardInterrupt: |
228 # intercept user pressing Ctrl+C |
357 # intercept user pressing Ctrl+C |
229 shutdown = True |
358 self.__shouldStop = True |
230 |
359 |
231 except Exception: |
360 except Exception: |
232 exctype, excval, exctb = sys.exc_info() |
361 exctype, excval, exctb = sys.exc_info() |
233 tbinfofile = io.StringIO() |
362 tbinfofile = io.StringIO() |
234 traceback.print_tb(exctb, None, tbinfofile) |
363 traceback.print_tb(exctb, None, tbinfofile) |
235 tbinfofile.seek(0) |
364 tbinfofile.seek(0) |
236 tbinfo = tbinfofile.read() |
365 tbinfo = tbinfofile.read() |
237 |
366 |
238 print(f"{str(exctype)} / {str(excval)} / {tbinfo}") |
367 print(f"{str(exctype)} / {str(excval)} / {tbinfo}") |
239 |
368 |
240 shutdown = True |
369 self.__shouldStop = True |
241 cleanExit = False |
370 cleanExit = False |
242 |
371 |
243 if self.__connection is not None: |
372 if self.__shouldStop: |
244 self.__connection.shutdown(socket.SHUT_RDWR) |
|
245 self.__connection.close() |
|
246 self.__connection = None |
|
247 |
|
248 if shutdown: |
|
249 # exit the outer loop and shut down the server |
373 # exit the outer loop and shut down the server |
250 self.__shutdown() |
374 self.__shutdown() |
251 break |
375 break |
252 |
376 |
253 return cleanExit |
377 return cleanExit |