|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the JSON based client base class. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 bytes = unicode |
|
13 import StringIO as io # __IGNORE_EXCEPTION__ |
|
14 except NameError: |
|
15 import io # __IGNORE_WARNING__ |
|
16 |
|
17 import sys |
|
18 import socket |
|
19 import select |
|
20 import traceback |
|
21 import time |
|
22 import json |
|
23 |
|
24 |
|
25 class JsonClient(object): |
|
26 """ |
|
27 Class implementing the JSON based client base class. |
|
28 """ |
|
29 def __init__(self, host, port): |
|
30 """ |
|
31 Constructor |
|
32 |
|
33 @param host ip address the background service is listening |
|
34 @type str |
|
35 @param port port of the background service |
|
36 @type int |
|
37 """ |
|
38 self.__connection = socket.create_connection((host, port)) |
|
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 line = self.__connection.recv(1024 * 1024, socket.MSG_PEEK) # 1M buffer |
|
63 |
|
64 eol = line.find(b'\n') |
|
65 |
|
66 if eol >= 0: |
|
67 size = eol + 1 |
|
68 |
|
69 # Now we know how big the line is, read it for real. |
|
70 line = self.__connection.recv(size).decode( |
|
71 'utf8', 'backslashreplace') |
|
72 try: |
|
73 commandDict = json.loads(line.strip()) |
|
74 except (TypeError, ValueError) as err: |
|
75 self.sendJson("ClientException", { |
|
76 "ExceptionType": "ProtocolError", |
|
77 "ExceptionValue": str(err), |
|
78 "ProtocolData": line.strip(), |
|
79 }) |
|
80 return |
|
81 |
|
82 method = commandDict["method"] |
|
83 params = commandDict["params"] |
|
84 self.handleCall(method, params) |
|
85 |
|
86 def handleCall(self, method, params): |
|
87 """ |
|
88 Public method to handle a method call from the server. |
|
89 |
|
90 Note: This is an empty implementation that must be overridden in |
|
91 derived classes. |
|
92 |
|
93 @param method requested method name |
|
94 @type str |
|
95 @param params dictionary with method specific parameters |
|
96 @type dict |
|
97 """ |
|
98 pass |
|
99 |
|
100 def run(self): |
|
101 """ |
|
102 Public method implementing the main loop of the client. |
|
103 """ |
|
104 try: |
|
105 while True: |
|
106 try: |
|
107 rrdy, wrdy, xrdy = select.select( |
|
108 [self.__connection], [], []) |
|
109 except (select.error, KeyboardInterrupt, socket.error): |
|
110 # just carry on |
|
111 continue |
|
112 |
|
113 if self.__connection in rrdy: |
|
114 self.__receiveJson() |
|
115 |
|
116 except Exception: |
|
117 exctype, excval, exctb = sys.exc_info() |
|
118 tbinfofile = io.StringIO() |
|
119 traceback.print_tb(exctb, None, tbinfofile) |
|
120 tbinfofile.seek(0) |
|
121 tbinfo = tbinfofile.read() |
|
122 del exctb |
|
123 self.sendJson("ClientException", { |
|
124 "ExceptionType": str(exctype), |
|
125 "ExceptionValue": str(excval), |
|
126 "Traceback": tbinfo, |
|
127 }) |
|
128 |
|
129 # Give time to process latest response on server side |
|
130 time.sleep(0.5) |
|
131 self.__connection.shutdown(socket.SHUT_RDWR) |
|
132 self.__connection.close() |