PageRenderTime 68ms CodeModel.GetById 36ms RepoModel.GetById 1ms app.codeStats 0ms

/extensions/crossfire/branches/0.3/test/crossfire_test_client.py

http://fbug.googlecode.com/
Python | 529 lines | 469 code | 25 blank | 35 comment | 3 complexity | 101835d5905a8d678ec61f58a1177ae8 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. #!/usr/bin/env python
  2. #
  3. # crossfire_test_client.py
  4. # Crossfire protocol test client implementation in python.
  5. #
  6. # M. G. Collins
  7. #
  8. # Copyright (c) 2009. All rights reserved.
  9. #
  10. # See license.txt for terms of usage.
  11. #
  12. # Usage:
  13. # $> python crossfire_test_client.py [<host>] <port> [--command=<command>[--args=<args>]]
  14. #
  15. # On Windows platforms, try installing the pyreadline package.
  16. #
  17. import json, readline, socket, sys, threading, time
  18. from optparse import OptionParser
  19. parser = OptionParser()
  20. # server host, defaults to localhost
  21. parser.add_option("-s", "--host",
  22. action="store", type="string", dest="serverHost", default="localhost",
  23. help="host name for the crossfire server, defaults to localhost")
  24. # server port, required
  25. parser.add_option("-p", "--port" ,
  26. action="store", type="int", dest="serverPort",
  27. help="port number the crossfire server is running on")
  28. # interactive command-line
  29. parser.add_option("-i", "--interactive",
  30. action="store_true", dest="interactive",
  31. help="starts the interactive command-line after connecting")
  32. # remote command to execute after handshake
  33. parser.add_option("-c", "--command",
  34. action="store", type="string", dest="execCommand",
  35. help="sends a command to the crossfire server after connecting")
  36. # optional arguments to command specified by -c
  37. parser.add_option("-a", "--args",
  38. action="store", type="string", dest="execArgs",
  39. help="optional arguments for the command specified by -c")
  40. # optional tool header to command specified by -c
  41. parser.add_option("-t", "--tool",
  42. action="store", type="string", dest="execTool", default="debugger",
  43. help="optional tool header for the command specified by -c")
  44. HANDSHAKE_STRING = "CrossfireHandshake"
  45. TOOL_STRING = "console,net,inspector,dom"
  46. #TOOL_STRING = "net,debugger"
  47. ###############################################################################
  48. current_seq = 0
  49. class CrossfireClient:
  50. def __init__(self, host, port):
  51. self.host = host
  52. self.port = port
  53. def start(self):
  54. #self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  55. #try:
  56. # self.socket.bind((self.host, self.port))
  57. #except socket.error:
  58. # print socket.error
  59. # quit()
  60. #self.socket.listen(1)
  61. #self.conn, addr = self.socket.accept()
  62. #self.conn = self.socket.connect((self.host, self.port))
  63. self.socket = socket.create_connection((self.host, self.port))
  64. self.socketCondition = threading.Condition()
  65. self.reader = PacketReader(self.socket, self.socketCondition)
  66. self.writer = PacketWriter(self.socket, self.socketCondition)
  67. self.socket.settimeout(10)
  68. self.socket.send(HANDSHAKE_STRING)
  69. self.socket.send("\r\n")
  70. self.socket.send(TOOL_STRING)
  71. self.socket.send("\r\n")
  72. self.waitHandshake()
  73. def stop(self):
  74. try:
  75. #self.conn.close()
  76. self.socket.close()
  77. #self.socketCondition.acquire()
  78. self.reader.join(0)
  79. self.writer.join(0)
  80. except (AttributeError, KeyboardInterrupt):
  81. pass
  82. def restart(self):
  83. self.stop()
  84. self.start()
  85. def waitHandshake(self):
  86. shake = ''
  87. print 'Waiting for Crossfire handshake...'
  88. try:
  89. shake = self.socket.recv(len(HANDSHAKE_STRING))
  90. print shake
  91. except socket.error, msg:
  92. print msg
  93. if shake == HANDSHAKE_STRING:
  94. print 'Received Crossfire handshake.'
  95. prev = curr = ""
  96. while prev != '\r' and curr != '\n':
  97. prev = curr
  98. try:
  99. curr = self.socket.recv(1)
  100. except socket.error:
  101. break
  102. tools = prev = curr = ""
  103. while prev != '\r' and curr != '\n':
  104. prev = curr
  105. try:
  106. curr = self.socket.recv(1)
  107. tools += str(curr)
  108. except socket.error:
  109. break
  110. print "Got tools: " + tools
  111. self.socketCondition.acquire()
  112. self.writer.start()
  113. self.reader.start()
  114. self.socketCondition.notifyAll()
  115. self.socketCondition.release()
  116. def sendPacket(self, packet):
  117. self.writer.addPacket(packet)
  118. def getPacket(self):
  119. if self.reader:
  120. return self.reader.getPacket()
  121. class PacketReader(threading.Thread):
  122. def __init__(self, conn, cv):
  123. threading.Thread.__init__(self)
  124. self.daemon = True
  125. self.packetQueue = []
  126. self.conn = conn
  127. self.cv = cv
  128. def getPacket(self):
  129. if len(self.packetQueue) > 0:
  130. return self.packetQueue.pop()
  131. def run(self):
  132. global current_seq
  133. while True:
  134. try:
  135. self.cv.acquire()
  136. headers = self.readPacketHeaders()
  137. if "Content-Length" in headers:
  138. print "\nheaders => " + str(headers)
  139. length = int(headers["Content-Length"])
  140. if length > 0:
  141. packet = self.readPacket(length)
  142. if packet:
  143. obj = json.loads(packet)
  144. current_seq = obj['seq'] +1
  145. if "tool" in headers:
  146. obj['tool'] = headers["tool"]
  147. self.packetQueue.append(obj)
  148. self.cv.notifyAll()
  149. self.cv.wait()
  150. except Exception as doh:
  151. print "Doh!"
  152. print doh
  153. break
  154. def readPacketHeaders(self):
  155. headers = {}
  156. readHeaders = True
  157. buff = prev = curr = ""
  158. while readHeaders:
  159. while prev != '\r' and curr != '\n':
  160. prev = curr
  161. try:
  162. curr = self.conn.recv(1)
  163. buff += str(curr)
  164. except socket.error:
  165. readHeaders = False
  166. break
  167. readHeaders = len(buff) > 2
  168. if readHeaders:
  169. ci = buff.find(":")
  170. name = buff[:ci]
  171. value = buff[ci+1:len(buff)-2]
  172. headers[name] = value
  173. # reset everything before next loop
  174. prev = curr = name = value = buff = ""
  175. return headers
  176. def readPacket(self, length):
  177. packet = ""
  178. read = offset = 0
  179. while read < length:
  180. if length-read < 4096:
  181. offset = length-read
  182. else:
  183. offset = 4096
  184. packet += self.conn.recv(offset)
  185. read += offset
  186. return packet
  187. class PacketWriter(threading.Thread):
  188. def __init__(self, conn, cv):
  189. threading.Thread.__init__(self)
  190. self.daemon = True
  191. self.packetQueue = []
  192. self.conn = conn
  193. self.cv = cv
  194. def addPacket(self, packet):
  195. self.packetQueue.append(packet)
  196. def run(self):
  197. while True:
  198. try:
  199. self.cv.acquire()
  200. if len(self.packetQueue) > 0:
  201. packet = self.packetQueue.pop()
  202. json_str = json.dumps(packet.packet)
  203. packet_string = "Content-Length:" + str(len(json_str)) + "\r\n"
  204. if packet.tool:
  205. packet_string += "tool:" + str(packet.tool) + "\r\n"
  206. packet_string += "\r\n" + json_str
  207. print "Sending a packet\n" + packet_string
  208. self.conn.send(packet_string)
  209. self.cv.notifyAll()
  210. self.cv.wait()
  211. except Error:
  212. #print err
  213. break
  214. class Command:
  215. def __init__(self, context_id, command_name, tool_name="debugger", **arguments):
  216. global current_seq
  217. current_seq += 1
  218. self.seq = current_seq
  219. self.command = command_name
  220. self.tool = tool_name
  221. self.packet = { "type": "request", "seq": self.seq, "contextId" : context_id, "command": command_name }
  222. if arguments:
  223. self.packet.update(arguments)
  224. Commands = [
  225. "createcontext",
  226. "listcontexts",
  227. "version",
  228. "continue",
  229. "suspend",
  230. "evaluate",
  231. "backtrace",
  232. "frame",
  233. "scopes",
  234. "scripts",
  235. "getbreakpoints",
  236. "setbreakpoints",
  237. "changebreakpoints",
  238. "deletebreakpoints",
  239. "inspect",
  240. "lookup",
  241. "gettools",
  242. "enabletools",
  243. "disabletools"
  244. ]
  245. COMMAND_PROMPT = 'Crossfire x> '
  246. class CommandLine(threading.Thread):
  247. def __init__(self):
  248. threading.Thread.__init__(self)
  249. self.daemon = True
  250. self.commands = []
  251. self.current_context = ""
  252. def setContext(self, ctx):
  253. self.current_context = ctx
  254. def getCommand(self):
  255. if len(self.commands) > 0:
  256. return self.commands.pop()
  257. def run(self):
  258. while True:
  259. try:
  260. line = raw_input(COMMAND_PROMPT)
  261. #line = sys.stdin.readline()
  262. if line:
  263. line = line.strip()
  264. space = line.find(' ')
  265. argstr = None
  266. tool = None
  267. if space == -1:
  268. command = line
  269. else:
  270. command = line[:space].strip()
  271. argstr = line[space:].strip()
  272. if "::" in command:
  273. ci = command.find("::")
  274. tool = command[:ci]
  275. command = command[ci+2:]
  276. if command in Commands:
  277. if command == "entercontext":
  278. self.current_context = argstr
  279. print "Entering context: " + self.current_context + "\n"
  280. else:
  281. args = {}
  282. if argstr:
  283. try:
  284. args = json.loads(argstr)
  285. except ValueError:
  286. print "Failed to parse arguments."
  287. self.commands.append(Command(self.current_context, command, tool, arguments=args))
  288. elif command:
  289. print "Unknown command: " + command
  290. except (ValueError, EOFError):
  291. break
  292. quit()
  293. if __name__ == "__main__":
  294. client = None
  295. commandLine = None
  296. currentContext = None
  297. def main():
  298. global client
  299. global commandLine
  300. global currentContext
  301. host = None
  302. port = None
  303. execCommand = None
  304. execArgs = None
  305. execTool = None
  306. interactive = False
  307. (options, args) = parser.parse_args()
  308. if options.serverPort:
  309. port = options.serverPort
  310. if options.serverHost:
  311. host = options.serverHost
  312. if options.execCommand:
  313. execCommand = options.execCommand
  314. execArgs = options.execArgs
  315. execTool = options.execTool
  316. arglen = len(args)
  317. if arglen == 1:
  318. #host = socket.gethostname()
  319. if port == None:
  320. port = args[0]
  321. elif execCommand == None:
  322. execCommand = args[0]
  323. elif execArgs == None:
  324. execArgs = args[0]
  325. elif arglen > 1:
  326. host = args[0]
  327. if port == None:
  328. port = args[1]
  329. elif execCommand == None:
  330. execCommand = args[1]
  331. elif execArgs == None:
  332. execArgs = args[1]
  333. if arglen > 2:
  334. if host == None:
  335. host = args[0]
  336. if port == None:
  337. port = args[1]
  338. if execCommand == None:
  339. execCommand = args[2]
  340. if arglen > 3 and execArgs == None:
  341. execArgs = args[3]
  342. # if we have no command to execute default to interactive mode
  343. if options.interactive or (execCommand == None):
  344. interactive = True
  345. if execArgs != None:
  346. try:
  347. execArgs = json.loads(execArgs)
  348. except ValueError:
  349. print "Failed to parse arguments."
  350. quit()
  351. if host and port:
  352. print 'Starting Crossfire client on ' + host + ':' + str(port)
  353. client = CrossfireClient(host, int(port))
  354. if interactive:
  355. commandLine = CommandLine()
  356. try:
  357. client.start()
  358. if interactive:
  359. commandLine.start()
  360. #print "Sending version command...\n"
  361. #command = Command("", "version")
  362. #client.sendPacket(command)
  363. print "Listing contexts...\n"
  364. command = Command("", "listcontexts")
  365. client.sendPacket(command)
  366. sendExecCommand = True
  367. while True:
  368. packet = client.getPacket()
  369. if packet:
  370. print
  371. json.dump(packet, sys.stdout, sort_keys=True, indent=2)
  372. print "\n" + COMMAND_PROMPT,
  373. #XXXmcollins: this makes the interactive mode work better on linux
  374. # but it fails on Windows with pyreadline.
  375. #readline.redisplay()
  376. ### try to set current context and/or look for 'closed' event
  377. if 'event' in packet:
  378. if packet['event'] == "closed":
  379. quit()
  380. elif (packet['event'] == "onContextCreated") or (packet['event'] == "onContextLoaded"):
  381. update_current_context(packet['data']['contextId'])
  382. elif packet['event'] == "onContextSelected":
  383. update_current_context(packet['data']['contextId'])
  384. elif 'command' in packet:
  385. if packet['command'] == "listcontexts":
  386. for ctx in packet['body']['contexts']:
  387. if ctx['current'] == True:
  388. update_current_context(ctx['contextId'])
  389. break
  390. ### if we got a response to the command, quit here
  391. if execCommand != None and packet['command'] == execCommand and not interactive:
  392. quit()
  393. ### if we have a context and we had a commmand to execute and send it here
  394. if execCommand != None and sendExecCommand == True and currentContext != None:
  395. command = Command(currentContext, execCommand, execTool, arguments=execArgs)
  396. client.sendPacket(command)
  397. sendExecCommand = False # only send command once
  398. ### read in next command
  399. if interactive:
  400. command = commandLine.getCommand()
  401. if command:
  402. print "\nSending command => " + command.command
  403. client.sendPacket(command)
  404. except (KeyboardInterrupt):
  405. pass
  406. else:
  407. print 'No host and/or port specified.'
  408. print 'Usage: $> python crossfire_test_client.py [<host>] <port>'
  409. quit()
  410. def update_current_context(ctx):
  411. global currentContext
  412. global commandLine
  413. currentContext = ctx
  414. print "\nSet current context to: " + currentContext
  415. if commandLine != None:
  416. commandLine.setContext(ctx)
  417. return ctx
  418. def quit():
  419. global client
  420. global commandLine
  421. print "\nShutting down..."
  422. try:
  423. client.stop()
  424. except Exception:
  425. pass
  426. print "Stopped."
  427. try:
  428. sys.stdin.flush()
  429. sys.stdin.close()
  430. sys.stdout.close()
  431. if commandLine:
  432. commandLine.join(1)
  433. except Exception:
  434. sys.exit(1)
  435. sys.exit(0)
  436. # kickstart
  437. main()