1
2
3
4 '''
5 This software is part of the TCPCom library.
6 It is Open Source Free Software, so you may
7 - run the code for any purpose
8 - study how the code works and adapt it to your needs
9 - integrate all or parts of the code in your own programs
10 - redistribute copies of the code
11 - improve the code and release your improvements to the public
12 However the use of the code is entirely your responsibility.
13 '''
14
15 from threading import Thread
16 import thread
17 import socket
18 import time
19 import sys
20
21 TCPCOM_VERSION = "1.26 - Jan. 17, 2018"
27 Thread.__init__(self)
28 self.server = server
29 self.timeout = timeout
30 self.count = 0
31
33 TCPServer.debug("TimeoutThread starting")
34 self.isRunning = True
35 isTimeout = False
36 while self.isRunning:
37 time.sleep(0.01)
38 self.count += 1
39 if self.count == 100 * self.timeout:
40 self.isRunning = False
41 isTimeout = True
42 if isTimeout:
43 TCPServer.debug("TimeoutThread terminated with timeout")
44 self.server.disconnect()
45 else:
46 TCPServer.debug("TimeoutThread terminated without timeout")
47
50
52 self.isRunning = False
53
56 '''
57 Class that represents a TCP socket based server.
58 '''
59 isVerbose = False
60 PORT_IN_USE = "PORT_IN_USE"
61 CONNECTED = "CONNECTED"
62 LISTENING = "LISTENING"
63 TERMINATED = "TERMINATED"
64 MESSAGE = "MESSAGE"
65
66 - def __init__(self, port, stateChanged, endOfBlock = '\0', isVerbose = False):
67 '''
68 Creates a TCP socket server that listens on TCP port
69 for a connecting client. The server runs in its own thread, so the
70 constructor returns immediately. State changes invoke the callback
71 onStateChanged().
72 @param port: the IP port where to listen (0..65535)
73 @param stateChange: the callback function to register
74 @param endOfBlock: character indicating end of a data block (default: '\0')
75 @param isVerbose: if true, debug messages are written to System.out, default: False
76 '''
77 Thread.__init__(self)
78 self.port = port
79 self.endOfBlock = endOfBlock
80 self.timeout = 0
81 self.stateChanged = stateChanged
82 TCPServer.isVerbose = isVerbose
83 self.isClientConnected = False
84 self.terminateServer = False
85 self.isServerRunning = False
86 self.start()
87
89 '''
90 Sets the maximum time (in seconds) to wait in blocking recv() for an incoming message. If the timeout is exceeded, the link to the client is disconnected.
91 (timeout <= 0: no timeout).
92 '''
93 if timeout <= 0:
94 self.timeout = 0
95 else:
96 self.timeout = timeout
97
99 TCPServer.debug("TCPServer thread started")
100 HOSTNAME = ""
101 self.conn = None
102 self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
103 self.serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
104 TCPServer.debug("Socket created")
105 try:
106 self.serverSocket.bind((HOSTNAME, self.port))
107 except socket.error as msg:
108 print "Fatal error while creating TCPServer: Bind failed.", msg[0], msg[1]
109 sys.exit()
110 try:
111 self.serverSocket.listen(10)
112 except:
113 print "Fatal error while creating TCPServer: Port", self.port, "already in use"
114 try:
115 self.stateChanged(TCPServer.PORT_IN_USE, str(self.port))
116 except Exception, e:
117 print "Caught exception in TCPServer.PORT_IN_USE:", e
118 sys.exit()
119
120 try:
121 self.stateChanged(TCPServer.LISTENING, str(self.port))
122 except Exception, e:
123 print "Caught exception in TCPServer.LISTENING:", e
124
125 self.isServerRunning = True
126
127 while True:
128 TCPServer.debug("Calling blocking accept()...")
129 conn, self.addr = self.serverSocket.accept()
130 if self.terminateServer:
131 self.conn = conn
132 break
133 if self.isClientConnected:
134 TCPServer.debug("Returning form blocking accept(). Client refused")
135 try:
136 conn.shutdown(socket.SHUT_RDWR)
137 except:
138 pass
139 conn.close()
140 continue
141 self.conn = conn
142 self.isClientConnected = True
143 self.socketHandler = ServerHandler(self, self.endOfBlock)
144 self.socketHandler.setDaemon(True)
145 self.socketHandler.start()
146 try:
147 self.stateChanged(TCPServer.CONNECTED, self.addr[0])
148 except Exception, e:
149 print "Caught exception in TCPServer.CONNECTED:", e
150 self.conn.close()
151 self.serverSocket.close()
152 self.isClientConnected = False
153 try:
154 self.stateChanged(TCPServer.TERMINATED, "")
155 except Exception, e:
156 print "Caught exception in TCPServer.TERMINATED:", e
157 self.isServerRunning = False
158 TCPServer.debug("TCPServer thread terminated")
159
161 '''
162 Closes the connection and terminates the server thread.
163 Releases the IP port.
164 '''
165 TCPServer.debug("Calling terminate()")
166 if not self.isServerRunning:
167 TCPServer.debug("Server not running")
168 return
169 self.terminateServer = True
170 TCPServer.debug("Disconnect by a dummy connection...")
171 if self.conn != None:
172 self.conn.close()
173 self.isClientConnected = False
174 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
175 client_socket.connect(('localhost', self.port))
176
178 '''
179 Closes the connection with the client and enters
180 the LISTENING state
181 '''
182 TCPServer.debug("Calling Server.disconnect()")
183 if self.isClientConnected:
184 self.isClientConnected = False
185 try:
186 self.stateChanged(TCPServer.LISTENING, str(self.port))
187 except Exception, e:
188 print "Caught exception in TCPServer.LISTENING:", e
189 TCPServer.debug("Shutdown socket now")
190 try:
191 self.conn.shutdown(socket.SHUT_RDWR)
192 except:
193 pass
194 self.conn.close()
195
197 '''
198 Sends the information msg to the client (as String, the character endOfBlock (defaut: ASCII 0) serves as end of
199 string indicator, it is transparently added and removed)
200 @param msg: the message to send
201 '''
202 TCPServer.debug("sendMessage() with msg: " + msg)
203 if not self.isClientConnected:
204 TCPServer.debug("Not connected")
205 return
206 try:
207 self.conn.sendall(msg + self.endOfBlock)
208 except:
209 TCPClient.debug("Exception in sendMessage()")
210
212 '''
213 Returns True, if a client is connected to the server.
214 @return: True, if the communication link is established
215 '''
216 return self.isClientConnected
217
219 '''
220 Blocks forever with little processor consumption until a keyboard interrupt is detected.
221 '''
222 try:
223 while True:
224 time.sleep(1)
225 except KeyboardInterrupt:
226 pass
227 self.terminate()
228
230 '''
231 Returns True, if the server is in TERMINATED state.
232 @return: True, if the server thread is terminated
233 '''
234 return self.terminateServer
235
236 @staticmethod
240
241 @staticmethod
243 '''
244 Returns the library version.
245 @return: the current version of the library
246 '''
247 return TCPCOM_VERSION
248
249 @staticmethod
250
252 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
253 try:
254
255 s.connect(('10.255.255.255', 1))
256 IP = s.getsockname()[0]
257 except:
258 IP = '127.0.0.1'
259 finally:
260 s.close()
261 return IP
262
265 - def __init__(self, server, endOfBlock):
266 Thread.__init__(self)
267 self.server = server
268 self.endOfBlock = endOfBlock
269
271 TCPServer.debug("ServerHandler started")
272 timeoutThread = None
273 if self.server.timeout > 0:
274 timeoutThread = TimeoutThread(self.server, self.server.timeout)
275 timeoutThread.start()
276 bufSize = 4096
277 try:
278 while True:
279 data = ""
280 reply = ""
281 isRunning = True
282 while not reply[-1:] == self.endOfBlock:
283 TCPServer.debug("Calling blocking conn.recv()")
284 reply = self.server.conn.recv(bufSize)
285 TCPServer.debug("Returned from conn.recv() with " + str(reply))
286 if reply == None or len(reply) == 0:
287 TCPServer.debug("conn.recv() returned None")
288 isRunning = False
289 break
290 data += reply
291 if not isRunning:
292 break
293 TCPServer.debug("Received msg: " + data + "; len: " + str(len(data)))
294 junk = data.split(self.endOfBlock)
295
296 for i in range(len(junk) - 1):
297 try:
298 self.server.stateChanged(TCPServer.MESSAGE, junk[i])
299 except Exception, e:
300 print "Caught exception in TCPServer.MESSAGE:", e
301 if not self.server.isClientConnected:
302 if timeoutThread != None:
303 timeoutThread.stop()
304 TCPServer.debug("Callback disconnected client. ServerHandler terminated")
305 return
306 if timeoutThread != None:
307 timeoutThread.reset()
308 except:
309 TCPServer.debug("Exception from blocking conn.recv(), Msg: " + str(sys.exc_info()[0]) + \
310 " at line # " + str(sys.exc_info()[-1].tb_lineno))
311 self.server.disconnect()
312 if timeoutThread != None:
313 timeoutThread.stop()
314 TCPServer.debug("ServerHandler terminated")
315
320 '''
321 Class that represents a TCP socket based client.
322 '''
323 isVerbose = False
324 CONNECTING = "CONNECTING"
325 SERVER_OCCUPIED = "SERVER_OCCUPIED"
326 CONNECTION_FAILED = "CONNECTION_FAILED"
327 CONNECTED = "CONNECTED"
328 DISCONNECTED = "DISCONNECTED"
329 MESSAGE = "MESSAGE"
330
331 - def __init__(self, ipAddress, port, stateChanged, isVerbose = False):
332 '''
333 Creates a TCP socket client prepared for a connection with a
334 TCPServer at given address and port.
335 @param host: the IP address of the host
336 @param port: the IP port where to listen (0..65535)
337 @param stateChanged: the callback function to register
338 @param isVerbose: if true, debug messages are written to System.out
339 '''
340 self.isClientConnected = False
341 self.isClientConnecting = False
342 self.ipAddress = ipAddress
343 self.port = port
344 self.stateChanged = stateChanged
345 self.checkRefused = False
346 self.isRefused = False
347
348 TCPClient.isVerbose = isVerbose
349
351 '''
352 Sends the information msg to the server (as String, the character \0
353 (ASCII 0) serves as end of string indicator, it is transparently added
354 and removed). For responseTime > 0 the method blocks and waits
355 for maximum responseTime seconds for a server reply.
356 @param msg: the message to send
357 @param responseTime: the maximum time to wait for a server reply (in s)
358 @return: the message or null, if a timeout occured
359 '''
360 TCPClient.debug("sendMessage() with msg = " + msg)
361 if not self.isClientConnected:
362 TCPClient.debug("sendMessage(): Connection closed.")
363 return None
364 reply = None
365 try:
366 msg += "\0";
367 rc = self.sock.sendall(msg)
368 if responseTime > 0:
369 reply = self._waitForReply(responseTime)
370 except:
371 TCPClient.debug("Exception in sendMessage()")
372 self.disconnect()
373
374 return reply
375
377 TCPClient.debug("Calling _waitForReply()")
378 self.receiverResponse = None
379 startTime = time.time()
380 while self.isClientConnected and self.receiverResponse == None and time.time() - startTime < responseTime:
381 time.sleep(0.01)
382 if self.receiverResponse == None:
383 TCPClient.debug("Timeout while waiting for reply")
384 else:
385 TCPClient.debug("Response = " + self.receiverResponse + " time elapsed: " + str(int(1000 * (time.time() - startTime))) + " ms")
386 return self.receiverResponse
387
389 '''
390 Creates a connection to the server (blocking until timeout).
391 @param timeout: the maximum time (in s) for the connection trial (0: for default timeout)
392 @return: True, if the connection is established; False, if the server
393 is not available or occupied
394 '''
395 if timeout == 0:
396 timeout = None
397 try:
398 self.stateChanged(TCPClient.CONNECTING, self.ipAddress + ":" + str(self.port))
399 except Exception, e:
400 print "Caught exception in TCPClient.CONNECTING:", e
401 try:
402 self.isClientConnecting = True
403 host = (self.ipAddress, self.port)
404 if self.ipAddress == "localhost" or self.ipAddress == "127.0.0.1":
405 timeout = None
406 self.sock = socket.create_connection(host, timeout)
407 self.sock.settimeout(None)
408 self.isClientConnecting = False
409 self.isClientConnected = True
410 except:
411 self.isClientConnecting = False
412 try:
413 self.stateChanged(TCPClient.CONNECTION_FAILED, self.ipAddress + ":" + str(self.port))
414 except Exception, e:
415 print "Caught exception in TCPClient.CONNECTION_FAILED:", e
416 TCPClient.debug("Connection failed.")
417 return False
418 ClientHandler(self)
419
420
421 self.checkRefused = True
422 self.isRefused = False
423 startTime = time.time()
424 while time.time() - startTime < 2 and not self.isRefused:
425 time.sleep(0.001)
426 if self.isRefused:
427 TCPClient.debug("Connection refused")
428 try:
429 self.stateChanged(TCPClient.SERVER_OCCUPIED, self.ipAddress + ":" + str(self.port))
430 except Exception, e:
431 print "Caught exception in TCPClient.SERVER_OCCUPIED:", e
432 return False
433
434 try:
435 self.stateChanged(TCPClient.CONNECTED, self.ipAddress + ":" + str(self.port))
436 except Exception, e:
437 print "Caught exception in TCPClient.CONNECTED:", e
438 TCPClient.debug("Successfully connected")
439 return True
440
442 '''
443 Closes the connection with the server.
444 '''
445 TCPClient.debug("Client.disconnect()")
446 if not self.isClientConnected:
447 TCPClient.debug("Connection already closed")
448 return
449 self.isClientConnected = False
450 TCPClient.debug("Closing socket")
451 try:
452 self.sock.shutdown(socket.SHUT_RDWR)
453 except:
454 pass
455 self.sock.close()
456
458 '''
459 Returns True during a connection trial.
460 @return: True, while the client tries to connect
461 '''
462 return self.isClientConnecting
463
465 '''
466 Returns True of client is connnected to the server.
467 @return: True, if the connection is established
468 '''
469 return self.isClientConnected
470
471 @staticmethod
475
476 @staticmethod
478 '''
479 Returns the library version.
480 @return: the current version of the library
481 '''
482 return TCPCOM_VERSION
483
487 Thread.__init__(self)
488 self.client = client
489 self.start()
490
492 TCPClient.debug("ClientHandler thread started")
493 while True:
494 try:
495 junk = self.readResponse().split("\0")
496
497
498 for i in range(len(junk) - 1):
499 try:
500 self.client.stateChanged(TCPClient.MESSAGE, junk[i])
501 except Exception, e:
502 print "Caught exception in TCPClient.MESSAGE:", e
503 except:
504 TCPClient.debug("Exception in readResponse() Msg: " + str(sys.exc_info()[0]) + \
505 " at line # " + str(sys.exc_info()[-1].tb_lineno))
506 if self.client.checkRefused:
507 self.client.isRefused = True
508 break
509 try:
510 self.client.stateChanged(TCPClient.DISCONNECTED, "")
511 except Exception, e:
512 print "Caught exception in TCPClient.DISCONNECTED:", e
513 TCPClient.debug("ClientHandler thread terminated")
514
516 TCPClient.debug("Calling readResponse")
517 bufSize = 4096
518 data = ""
519 while not data[-1:] == "\0":
520 try:
521 reply = self.client.sock.recv(bufSize)
522 if len(reply) == 0:
523 TCPClient.debug("recv returns null length")
524 raise Exception("recv returns null length")
525 except:
526 TCPClient.debug("Exception from blocking conn.recv(), Msg: " + str(sys.exc_info()[0]) + \
527 " at line # " + str(sys.exc_info()[-1].tb_lineno))
528 raise Exception("Exception from blocking sock.recv()")
529 data += reply
530 self.receiverResponse = data[:-1]
531 return data
532
536
538 return "HTTP/1.1 501 OK\r\nServer: " + self.serverName + "\r\nConnection: Closed\r\n"
539
541 return "HTTP/1.1 200 OK\r\nServer: " + self.serverName + "\r\nContent-Length: %d\r\nContent-Type: text/html\r\nConnection: Closed\r\n\r\n"
542
545
546 - def __init__(self, requestHandler, serverName = "PYSERVER", port = 80, isVerbose = False):
547 '''
548
549 Creates a HTTP server (inherited from TCPServer) that listens for a connecting client on given port (default = 80).
550 Starts a thread that handles and returns HTTP GET requests. The HTTP respoonse header reports the given server name
551 (default: "PYSERVER")
552
553 requestHandler() is a callback function called when a GET request is received.
554 Signature: msg, stateHandler = requestHandler(clientIP, filename, params)
555
556 Parameters:
557 clientIP: the client's IP address in dotted format
558 filename: the requested filename with preceeding '/'
559 params: a tuple with format: ((param_key1, param_value1), (param_key2, param_value2), ...) (all items are strings)
560
561 Return values:
562 msg: the HTTP text response (the header is automatically created)
563 stateHandler: a callback function that is invoked immediately after the reponse is sent.
564 If stateHandler = None, nothing is done. The function may include longer lasting server
565 actions or a wait time, if sensors are not immediately ready for a new measurement.
566
567 Call terminate() to stop the server. The connection is closed by the server at the end of each response. If the client connects,
568 but does not send a request within 5 seconds, the connection is closed by the server.
569 '''
570 try:
571 registerStopFunction(self.onStop)
572 except:
573 pass
574
575 TCPServer.__init__(self, port, stateChanged = self.onStateChanged, endOfBlock = '\n', isVerbose = isVerbose)
576 self.serverName = serverName
577 self.requestHandler = requestHandler
578 self.port = port
579 self.verbose = isVerbose
580 self.timeout = 5
581 self.clientIP = ""
582
584 '''
585 Returns the dotted IP of a connected client. If no client is connected, returns empty string.
586 '''
587 return self.clientIP
588
590 if state == "CONNECTED":
591 self.clientIP = msg
592 self.debug("Client " + msg + " connected.")
593 elif state == "DISCONNECTED":
594 self.clientIP = ""
595 self.debug("Client disconnected.")
596 elif state == "LISTENING":
597 self.clientIP = ""
598 self.debug("LISTENING")
599 elif state == "MESSAGE":
600 self.debug("request: " + msg)
601 if len(msg) != 0:
602 filename, params = self._parseURL(msg)
603 if filename == None:
604 self.sendMessage(self.getHeader1())
605 else:
606 text, stateHandler = self.requestHandler(self.clientIP, filename, params)
607 self.sendMessage(self.getHeader2() % (len(text)))
608 self.sendMessage(text)
609 if stateHandler != None:
610 try:
611 stateHandler()
612 except:
613 print "Exception in stateHandler()"
614 else:
615 self.sendMessage(self.getHeader1())
616 self.disconnect()
617
619 lines = request.split('\n')
620 params = []
621 for line in lines:
622 if line[0:4] == 'GET ':
623 url = line.split()[1].strip()
624 i = url.find('?')
625 if i != -1:
626 filename = url[0:i].strip()
627 params = []
628 urlParam = url[i + 1:]
629 for item in urlParam.split('&'):
630 i = item.find('=')
631 key = item[0:i]
632 value = item[i+1:]
633 params.append((key, value))
634 return filename, tuple(params)
635 return url.strip(), tuple([])
636 return None, tuple([])
637
639 if self.verbose:
640 print(" HTTPServer-> " + msg)
641
642 @staticmethod
648