blob: fb75938c1fc948300116797ca2a06a3c8a7d72c5 [file] [log] [blame]
Bram Moolenaard7ece102016-02-02 23:23:02 +01001#!/usr/bin/python
2#
3# Server that will accept connections from a Vim channel.
4# Run this server and then in Vim you can open the channel:
5# :let handle = ch_open('localhost:8765', 'json')
6#
7# Then Vim can send requests to the server:
8# :let response = ch_sendexpr(handle, 'hello!')
9#
Bram Moolenaard7ece102016-02-02 23:23:02 +010010# See ":help channel-demo" in Vim.
11#
12# This requires Python 2.6 or later.
13
14from __future__ import print_function
15import json
16import socket
17import sys
18import threading
19
20try:
21 # Python 3
22 import socketserver
23except ImportError:
24 # Python 2
25 import SocketServer as socketserver
26
Bram Moolenaard7ece102016-02-02 23:23:02 +010027class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
28
29 def handle(self):
30 print("=== socket opened ===")
Bram Moolenaard7ece102016-02-02 23:23:02 +010031 while True:
32 try:
Bram Moolenaar608a8912016-02-03 22:39:51 +010033 received = self.request.recv(4096).decode('utf-8')
Bram Moolenaard7ece102016-02-02 23:23:02 +010034 except socket.error:
35 print("=== socket error ===")
36 break
37 except IOError:
38 print("=== socket closed ===")
39 break
Bram Moolenaar608a8912016-02-03 22:39:51 +010040 if received == '':
Bram Moolenaard7ece102016-02-02 23:23:02 +010041 print("=== socket closed ===")
42 break
Bram Moolenaar608a8912016-02-03 22:39:51 +010043 print("received: {}".format(received))
Bram Moolenaard7ece102016-02-02 23:23:02 +010044
Bram Moolenaare7bed622016-02-03 22:20:29 +010045 # We may receive two messages at once. Take the part up to the
46 # matching "]" (recognized by finding "][").
Bram Moolenaar608a8912016-02-03 22:39:51 +010047 todo = received
48 while todo != '':
49 splitidx = todo.find('][')
Bram Moolenaare7bed622016-02-03 22:20:29 +010050 if splitidx < 0:
Bram Moolenaar608a8912016-02-03 22:39:51 +010051 used = todo
52 todo = ''
Bram Moolenaard7ece102016-02-02 23:23:02 +010053 else:
Bram Moolenaar608a8912016-02-03 22:39:51 +010054 used = todo[:splitidx + 1]
55 todo = todo[splitidx + 1:]
56 if used != received:
57 print("using: {}".format(used))
Bram Moolenaard7ece102016-02-02 23:23:02 +010058
Bram Moolenaare7bed622016-02-03 22:20:29 +010059 try:
Bram Moolenaar608a8912016-02-03 22:39:51 +010060 decoded = json.loads(used)
Bram Moolenaare7bed622016-02-03 22:20:29 +010061 except ValueError:
62 print("json decoding failed")
63 decoded = [-1, '']
Bram Moolenaard7ece102016-02-02 23:23:02 +010064
Bram Moolenaare7bed622016-02-03 22:20:29 +010065 # Send a response if the sequence number is positive.
66 if decoded[0] >= 0:
67 if decoded[1] == 'hello!':
68 # simply send back a string
69 response = "got it"
70 elif decoded[1] == 'make change':
Bram Moolenaar66624ff2016-02-03 23:59:43 +010071 # Send two ex commands at the same time, before
72 # replying to the request.
Bram Moolenaare7bed622016-02-03 22:20:29 +010073 cmd = '["ex","call append(\\"$\\",\\"added1\\")"]'
74 cmd += '["ex","call append(\\"$\\",\\"added2\\")"]'
75 print("sending: {}".format(cmd))
Bram Moolenaar3b05b132016-02-03 23:25:07 +010076 self.request.sendall(cmd.encode('utf-8'))
Bram Moolenaare7bed622016-02-03 22:20:29 +010077 response = "ok"
78 elif decoded[1] == 'eval-works':
79 # Send an eval request. We ignore the response.
80 cmd = '["eval","\\"foo\\" . 123", -1]'
81 print("sending: {}".format(cmd))
Bram Moolenaar3b05b132016-02-03 23:25:07 +010082 self.request.sendall(cmd.encode('utf-8'))
Bram Moolenaare7bed622016-02-03 22:20:29 +010083 response = "ok"
84 elif decoded[1] == 'eval-fails':
85 # Send an eval request that will fail.
86 cmd = '["eval","xxx", -2]'
87 print("sending: {}".format(cmd))
Bram Moolenaar3b05b132016-02-03 23:25:07 +010088 self.request.sendall(cmd.encode('utf-8'))
Bram Moolenaare7bed622016-02-03 22:20:29 +010089 response = "ok"
Bram Moolenaar66624ff2016-02-03 23:59:43 +010090 elif decoded[1] == 'eval-bad':
91 # Send an eval request missing the third argument.
92 cmd = '["eval","xxx"]'
93 print("sending: {}".format(cmd))
94 self.request.sendall(cmd.encode('utf-8'))
95 response = "ok"
Bram Moolenaare7bed622016-02-03 22:20:29 +010096 elif decoded[1] == 'eval-result':
97 # Send back the last received eval result.
98 response = last_eval
99 elif decoded[1] == '!quit!':
100 # we're done
Bram Moolenaarb3e2f002016-02-04 00:11:37 +0100101 self.server.shutdown()
102 break
Bram Moolenaare7bed622016-02-03 22:20:29 +0100103 elif decoded[1] == '!crash!':
104 # Crash!
105 42 / 0
106 else:
107 response = "what?"
108
109 encoded = json.dumps([decoded[0], response])
110 print("sending: {}".format(encoded))
Bram Moolenaar3b05b132016-02-03 23:25:07 +0100111 self.request.sendall(encoded.encode('utf-8'))
Bram Moolenaare7bed622016-02-03 22:20:29 +0100112
113 # Negative numbers are used for "eval" responses.
114 elif decoded[0] < 0:
115 last_eval = decoded
Bram Moolenaarfcb1e3d2016-02-03 21:32:46 +0100116
Bram Moolenaard7ece102016-02-02 23:23:02 +0100117class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
118 pass
119
120if __name__ == "__main__":
121 HOST, PORT = "localhost", 0
122
123 server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
124 ip, port = server.server_address
125
126 # Start a thread with the server -- that thread will then start one
127 # more thread for each request
128 server_thread = threading.Thread(target=server.serve_forever)
129
130 # Exit the server thread when the main thread terminates
Bram Moolenaard7ece102016-02-02 23:23:02 +0100131 server_thread.start()
132
133 # Write the port number in Xportnr, so that the test knows it.
134 f = open("Xportnr", "w")
135 f.write("{}".format(port))
136 f.close()
137
Bram Moolenaard7ece102016-02-02 23:23:02 +0100138 print("Listening on port {}".format(port))
Bram Moolenaarb3e2f002016-02-04 00:11:37 +0100139
140 # Main thread terminates, but the server continues running
141 # until server.shutdown() is called.