__init__.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. #!/usr/bin/env python3.5
  2. # Copyright 2017 Digital
  3. #
  4. # This file is part of DigiLib.
  5. #
  6. # DigiLib is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # DigiLib is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with DigiLib. If not, see <http://www.gnu.org/licenses/>.
  18. import atexit
  19. import logging
  20. import logging.handlers
  21. import os
  22. import queue
  23. import select
  24. import socket
  25. import sys
  26. import threading
  27. import time
  28. import traceback
  29. import blinker
  30. import curio
  31. import digilib.misc
  32. lclient = logging.getLogger(__name__+".client")
  33. lserver = logging.getLogger(__name__+".server")
  34. lschat = logging.getLogger(__name__+".server.chat")
  35. lcchat = logging.getLogger(__name__+".client.chat")
  36. _tasks = []
  37. class ConnHandlerBase(object):
  38. def __init__(self, socket, addr, server):
  39. self.status = "init"
  40. super(ConnHandlerBase, self).__init__()
  41. self.socket = socket
  42. self.addr = addr
  43. self.server = server
  44. self.block_size = 1024
  45. # self.welcome_client()
  46. self.status="connected"
  47. async def welcome_client(self):
  48. pass
  49. async def handle(self, data):
  50. return
  51. async def recv(self):
  52. data_received = await self.socket.recv(self.block_size)
  53. data_decoded = data_received.decode("utf-8")
  54. return data_decoded
  55. async def send(self, msg, log_msg=False):
  56. msg_encoded = bytes(msg, "utf-8")
  57. if log_msg:
  58. lschat.info("server:"+log_msg)
  59. else:
  60. lschat.info("Server:"+msg)
  61. try:
  62. await self.socket.send(msg_encoded)
  63. return True
  64. except Exception as e:
  65. lserver.error(e, exc_info=True)
  66. return False
  67. async def close(self):
  68. self.status = "closed"
  69. try:
  70. await self.socket.shutdown(0)
  71. except:
  72. lserver.debug("error during socket shutdown")
  73. try:
  74. await self.socket.close()
  75. except:
  76. lserver.debug("error closing socket")
  77. class ConnHandlerEcho(ConnHandlerBase):
  78. def __init__(self, socket, addr, server):
  79. self.status = "init"
  80. self.super_class = super(ConnHandlerEcho, self)
  81. self.super_class.__init__(socket, addr, server)
  82. self.server = server
  83. def welcome_client(self):
  84. self.send("welcome to the client")
  85. def handle(self, data):
  86. lschat.info("Client:"+data)
  87. for h in list(set(self.server.connection_handler)-{self}):
  88. h.send(data)
  89. class Server(object):
  90. """
  91. Server opens either an unix or an inet connection. for every client which connects a new ClientHandler class is created.
  92. """
  93. def __init__(self,
  94. host,
  95. port=None,
  96. af_family="AF_INET",
  97. log_ip=False,
  98. max_allowed_clients=5,
  99. handler_class=None,
  100. handler_kwargs={},
  101. ):
  102. super(Server, self).__init__()
  103. # set to true when the server shuts down, for instance after a
  104. # fatal exception
  105. self.exit_event = False
  106. # on which hostname or ip address the connection will be opened
  107. self.host = host
  108. # on which port the connection will be opened
  109. self.port = port
  110. # what AF_INET family to use, either AF_INET or AF_UNIX (file socket)
  111. self.af_family = af_family
  112. # whether ip addresses will be logged
  113. self.log_ip = log_ip
  114. # number of maximum client connection at a time
  115. self.max_allowed_clients = max_allowed_clients
  116. # this handler class will be used when creating a new handler
  117. self.handler_class = handler_class
  118. # the kwargs passed to the handler's __init__ function
  119. self.handler_kwargs = handler_kwargs
  120. # don't make the socket yet, we don't need right now. will be created
  121. # when the start method is called
  122. # self.socket = self.make_socket()
  123. self.socket = None
  124. # create a task group for handlers, so we can easily cancel/terminate
  125. # them all at once
  126. self.handle_tasks = curio.TaskGroup(name="tg_handle_clients")
  127. # list of all active connection handlers
  128. self.connection_handler = []
  129. # these aren't actually used, I will remove them soon
  130. # self.conn_to_addr = {}
  131. # self.addr_to_conn = {}
  132. # self.conn_to_handler ={}
  133. # self.handler_to_conn = {}
  134. # this was used for the select.select approach. we use the async approach now, let's remove it
  135. # self.read_sockets_expected = [self.socket]
  136. # register our cleanup method to be executed when the program exits.
  137. # the cleanup function unregisters itself, so it won't get executed twice when the user called it befor the program exites
  138. atexit.register(self.shutdown)
  139. def make_socket(self):
  140. """
  141. factory method for sockets.
  142. this method makes a normal socket and wraps it in a curi.io.Socket wrapper
  143. """
  144. lserver.debug("making a {} socket".format(self.af_family))
  145. if self.af_family == "AF_INET":
  146. s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  147. elif self.af_family == "AF_UNIX":
  148. s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
  149. else:
  150. raise ValueError(
  151. "AF_FAMILY '{}' not supported!".format(
  152. self.af_family
  153. )
  154. )
  155. s = curio.io.Socket(s)
  156. return s
  157. def make_handler(self, conn, addr):
  158. """
  159. factory method for handlers.
  160. this method creates a handler object from self.handler_class and self.handler_kwargs
  161. """
  162. return self.handler_class(conn, addr, self, **self.handler_kwargs)
  163. # DEPRECATED
  164. # def register_conn(self, conn, addr):
  165. # # if self.log_ip:
  166. # # lserver.info("New connection from {} on port {}".format(*addr))
  167. # self.read_sockets_expected.append(conn)
  168. # if addr:
  169. # self.conn_to_addr[conn] = addr
  170. # self.addr_to_conn[addr] = conn
  171. #
  172. # def unregister_conn(self, conn):
  173. # self.read_sockets_expected.remove(conn)
  174. # addr = self.conn_to_addr.get(conn, False)
  175. # if addr:
  176. # del self.addr_to_conn[addr]
  177. # del self.conn_to_addr[conn]
  178. #
  179. # def register_handler(self, handler, conn):
  180. # self.connection_handler.append(handler)
  181. # self.conn_to_handler[conn] = handler
  182. # self.handler_to_conn[handler] = conn
  183. #
  184. # def unregister_handler(self, handler, conn):
  185. # self.connection_handler.remove(handler)
  186. # del self.conn_to_handler[conn]
  187. # del self.handler_to_conn[handler]
  188. def setup(self):
  189. """
  190. creates a scoket, opens the connection and starts listening for
  191. clients. this method does not block, it returns after starting to
  192. listen.
  193. """
  194. lserver.info("setting up server")
  195. self.socket = self.make_socket()
  196. if self.af_family == "AF_INET":
  197. self.socket.bind((self.host, self.port))
  198. elif self.af_family == "AF_UNIX":
  199. if os.path.exists(self.host):
  200. lserver.debug("file already exists")
  201. lserver.debug("attempting to remove it")
  202. os.remove(self.host)
  203. self.socket.bind(self.host)
  204. self.socket.listen(self.max_allowed_clients)
  205. def shutdown(self):
  206. """
  207. This method properly shuts down the sockets and closes them.
  208. it unregisters itself from atexit, so it doesn't get executed twice
  209. when it was manually called before to program exits.
  210. """
  211. def error_handler(func,*args,log_text="error",async_=False,**kwargs):
  212. try:
  213. if async_:
  214. coro = func(*args,**kwargs)
  215. lserver.debug("{} {}".format(func,coro))
  216. else:
  217. func(*args,**kwargs)
  218. except Exception as exc:
  219. lserver.debug("error occured during "+log_text,exc_info=exc)
  220. atexit.unregister(self.shutdown)
  221. lserver.info("shutting down server")
  222. error_handler(
  223. self.handle_tasks.cancel_remaining,log_text="handler cancel",async_=True)
  224. # check if there is actually a socket. if the shutdown method is
  225. # executed before the start method, there is no socket.
  226. if self.socket:
  227. error_handler(self.socket.shutdown,log_text="socket shutdown")
  228. error_handler(self.socket.close,log_text="socket close")
  229. def start(self):
  230. """
  231. this method starts the server. it is blocking.
  232. """
  233. self.setup()
  234. curio.run(self.run)
  235. async def run(self):
  236. """
  237. this method is the main loop of the Server. it waits for new client
  238. connections and creates a handle task for each of them. it does not
  239. receive or send anything.
  240. """
  241. lserver.debug("entering main loop")
  242. while ( not self.exit_event ):
  243. lserver.debug("waiting for client to connect")
  244. # wait for a client to connect
  245. conn,addr = await self.socket.accept()
  246. if self.log_ip:
  247. lserver.info(
  248. "new client connection, {}:{}!".format(*addr))
  249. else:
  250. lserver.info("a new client connected, let's handle it")
  251. handler = self.make_handler(conn, addr)
  252. # self.register_conn(conn, addr)
  253. # self.register_handler(handler, conn)
  254. await self.handle_tasks.spawn(self.handle_client(conn,handler))
  255. async def handle_client(self,socket,handler):
  256. """
  257. This method waits for the client to send something and calls the
  258. ClientHandler's handle method. there is a handle_client method running for each client connected.
  259. """
  260. while True:
  261. try:
  262. if self.log_ip:
  263. lserver.debug("waiting for {} to send something"
  264. .format(socket.getsockname()))
  265. else:
  266. lserver.debug("waiting for the client to send something")
  267. # wait for the client to send something
  268. data = await handler.recv()
  269. # if there is no data the client disconnected. this is a
  270. # tcp protocoll specification.
  271. if not data:
  272. if self.log_ip:
  273. lserver.info("the connection to {} was closed"
  274. .format(socket.getsockname()))
  275. else:
  276. lserver.info("the connection to the client was closed")
  277. # self.unregister_handler(handler, socket)
  278. # self.unregister_conn(socket)
  279. await handler.close()
  280. # break out of the loop. don't return because we need to
  281. # do cleanup
  282. break
  283. else:
  284. # don't strip the data of its whitespaces, since they may
  285. # be important.
  286. lschat.info("Client:"+data.rstrip())
  287. coro = handler.handle(data)
  288. task = await curio.spawn(coro)
  289. except Exception as e:
  290. lserver.error(e, exc_info=True)
  291. # let's sleep a bit, in case something is broken and the
  292. # loop throws an exception every time
  293. await curio.sleep(0.01)
  294. # if a task exits and hasn't been joined, curio prints a warning.
  295. # we don't want the warning, so let's join the current task for 0
  296. # seconds. instead of task.join() we use task.wait(). the only
  297. # difference is that wait doesn't throw a exception if the task was
  298. # stopped or crashed
  299. cur_task = await curio.current_task()
  300. await curio.ignore_after(0,cur_task.wait)
  301. class Client(threading.Thread):
  302. """docstring for Client"""
  303. is_connecting = False
  304. is_connected = False
  305. status = "uninitialized"
  306. def __init__(self,
  307. host,
  308. port=None,
  309. af_family="AF_INET",
  310. handle_data_func=None,
  311. error_handler=None,
  312. block_size=1024,
  313. ):
  314. self.super_class = super(Client, self)
  315. self.super_class.__init__()
  316. self.name = "Client"
  317. self.exit_event = False
  318. self.host = host
  319. self.port = port
  320. self.af_family = af_family
  321. self.block_size = block_size
  322. self.handle_data_func = handle_data_func
  323. self.is_connected = False
  324. self.error_handler = error_handler
  325. # self.socket = self.make_socket()
  326. self.socket = None
  327. self.status = "disconnected"
  328. def connect(self):
  329. self.status = "connecting"
  330. self.socket = self.make_socket()
  331. lclient.info(
  332. "connecting to socket '{}' of type {}".format(
  333. self.host,
  334. self.af_family
  335. )
  336. )
  337. try:
  338. if self.af_family == "AF_INET":
  339. self.socket.connect((self.host, self.port))
  340. elif self.af_family == "AF_UNIX":
  341. if os.path.exists(self.host):
  342. self.socket.connect(self.host)
  343. else:
  344. lclient.warn("File not found. Aborting.")
  345. return
  346. self.is_connected = True
  347. self.status = "connected"
  348. lclient.info("connected")
  349. return True
  350. except Exception as e:
  351. lclient.debug(e, exc_info=True)
  352. if type(e) is ConnectionRefusedError:
  353. lclient.info("failed to connect to socket '{}'".format(self.host))
  354. self.disconnect()
  355. return False
  356. def disconnect(self):
  357. lclient.info("disconnecting from socket '{}'".format(self.host))
  358. self.is_connected = False
  359. self.status = "disconnected"
  360. if self.socket:
  361. try:
  362. self.socket.shutdown(socket.SHUT_RDWR)
  363. except Exception as e:
  364. lclient.error(e)
  365. try:
  366. self.socket.close()
  367. except Exception as e:
  368. lclient.error("error occured while closing the socket, " +
  369. "maybe it is already closed",exc_info=e)
  370. del self.socket
  371. self.socket = None
  372. def handle_data(self, data_received):
  373. data_decoded = data_received.decode("utf-8")
  374. lcchat.info("Server: "+data_decoded)
  375. if self.handle_data_func:
  376. try:
  377. self.handle_data_func(data_decoded)
  378. except Exception as e:
  379. lclient.error(e, exc_info=True)
  380. def is_running(self):
  381. return (self in threading.enumerate())
  382. def make_socket(self):
  383. lclient.info("creating a {} socket".format(self.af_family))
  384. if self.af_family == "AF_INET":
  385. s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  386. elif self.af_family == "AF_UNIX":
  387. s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
  388. else:
  389. raise ValueError(
  390. "AF_FAMILY '{}' not supported!".format(
  391. self.af_family
  392. )
  393. )
  394. return s
  395. def main_loop(self):
  396. lclient.debug("starting main loop")
  397. while ( not self.exit_event ):
  398. if not self.status in ["connected"]:
  399. time.sleep(0.1)
  400. continue
  401. # print(0)
  402. read_confirmed, write_confirmed, exc_confirmed \
  403. = select.select(
  404. [self.socket],
  405. [],
  406. [self.socket],
  407. 1
  408. )
  409. if self.socket in exc_confirmed:
  410. self.is_connected = False
  411. lclient.warning("socket is expected to corrupt, exiting")
  412. self.disconnect()
  413. # self.stop()
  414. break
  415. elif self.socket in read_confirmed:
  416. try:
  417. data_received = self.read_from_socket()
  418. if data_received == b'':
  419. lclient.info("connection is broken, closing socket exiting")
  420. self.disconnect()
  421. # self.stop()
  422. # break
  423. else:
  424. try:
  425. self.handle_data(data_received)
  426. except Exception as e:
  427. lserver.error(
  428. "Error while handling data",
  429. exc_info=e
  430. )
  431. except Exception as e:
  432. lclient.error(e, exc_info=True)
  433. if type(e) is OSError:
  434. self.is_connected = False
  435. lclient.warn("connection broken, exiting")
  436. self.disconnect()
  437. # self.stop()
  438. # break
  439. else:
  440. raise
  441. else:
  442. time.sleep(0.1)
  443. def read_from_socket(self):
  444. data_received = self.socket.recv(self.block_size)
  445. return data_received
  446. def run(self):
  447. # self.connect()
  448. if self.error_handler:
  449. self.error_handler(self.main_loop)
  450. else:
  451. self.main_loop()
  452. def send(self, msg):
  453. msg = msg.rstrip()
  454. msg_encoded = bytes(msg+"\r\n", "utf-8")
  455. try:
  456. lcchat.info("Client: "+msg)
  457. self.socket.send(msg_encoded)
  458. except Exception as e:
  459. self.is_connected = False
  460. lclient.error(e, exc_info=True)
  461. self.status = "shutdown"
  462. def setup(self):
  463. pass
  464. def stop(self,reason=None):
  465. self.disconnect()
  466. self.exit_event = True
  467. if reason:
  468. print(reason)
  469. class AsyncClient(object):
  470. """docstring for Client"""
  471. is_connecting = False
  472. is_connected = False
  473. status = "uninitialized"
  474. def __init__(self,
  475. host,
  476. port=None,
  477. af_family="AF_INET",
  478. handle_data_func=None,
  479. error_handler=None,
  480. block_size=1024,
  481. ):
  482. self.super_class = super(AsyncClient, self)
  483. self.super_class.__init__()
  484. self.name = "Client"
  485. self.exit_event = False
  486. self.host = host
  487. self.port = port
  488. self.af_family = af_family
  489. self.block_size = block_size
  490. self.handle_data_func = handle_data_func
  491. self.is_connected = False
  492. self.error_handler = error_handler
  493. self.socket = None
  494. self.status = "disconnected"
  495. def connect(self):
  496. self.status = "connecting"
  497. self.socket = self.make_socket()
  498. lclient.info("connecting to socket '{}' of type {}".format(
  499. self.host,self.af_family))
  500. try:
  501. if self.af_family == "AF_INET":
  502. self.socket.connect((self.host, self.port))
  503. elif self.af_family == "AF_UNIX":
  504. self.socket.connect(self.host)
  505. self.is_connected = True
  506. self.status = "connected"
  507. lclient.info("connected")
  508. return True
  509. except Exception as e:
  510. lclient.debug(e, exc_info=True)
  511. if type(e) is ConnectionRefusedError:
  512. lclient.info("failed to connect to socket '{}'".format(self.host))
  513. self.disconnect()
  514. return False
  515. def disconnect(self):
  516. lclient.info("disconnecting from socket '{}'".format(self.host))
  517. self.is_connected = False
  518. self.status = "disconnected"
  519. if self.socket:
  520. try:
  521. self.socket.shutdown(socket.SHUT_RDWR)
  522. except Exception as e:
  523. lclient.error(e)
  524. try:
  525. self.socket.close()
  526. except Exception as e:
  527. lclient.error("error occured while closing the socket, " +
  528. "maybe it is already closed",exc_info=e)
  529. del self.socket
  530. self.socket = None
  531. def handle_data(self, data_received):
  532. data_decoded = data_received.decode("utf-8")
  533. lcchat.info("Server: "+data_decoded)
  534. if self.handle_data_func:
  535. try:
  536. self.handle_data_func(data_decoded)
  537. except Exception as e:
  538. lclient.error(e, exc_info=True)
  539. def is_running(self):
  540. return (self in threading.enumerate())
  541. def make_socket(self):
  542. lclient.info("creating a {} socket".format(self.af_family))
  543. if self.af_family == "AF_INET":
  544. s = trio.socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  545. elif self.af_family == "AF_UNIX":
  546. s = trio.socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
  547. else:
  548. raise ValueError(
  549. "AF_FAMILY '{}' not supported!".format(
  550. self.af_family
  551. )
  552. )
  553. return s
  554. async def read_from_socket(self):
  555. data_received = await self.socket.recv(self.block_size)
  556. return data_received
  557. async def recv(self):
  558. data_received = await self.socket.recv(self.block_size)
  559. data_decoded = data_received.decode("utf-8")
  560. return data_decoded
  561. async def run(self):
  562. lclient.debug("starting main loop")
  563. while ( not self.exit_event ):
  564. if not self.status in ["connected"]:
  565. time.sleep(0.1)
  566. continue
  567. # if self.socket in exc_confirmed:
  568. # self.is_connected = False
  569. # lclient.warning("socket is expected to corrupt, exiting")
  570. # self.disconnect()
  571. # # self.stop()
  572. # break
  573. # elif self.socket in read_confirmed:
  574. try:
  575. data = await self.read_from_socket()
  576. if not data:
  577. lclient.info("connection closed")
  578. self.disconnect()
  579. else:
  580. self.handle_data(data)
  581. except Exception as e:
  582. lclient.error(e, exc_info=True)
  583. if type(e) is OSError:
  584. self.is_connected = False
  585. lclient.warn("connection broken, exiting")
  586. self.disconnect()
  587. else:
  588. raise
  589. async def start(self,connect=False):
  590. if connect:
  591. self.connect()
  592. if self.error_handler:
  593. self.error_handler(self.run)
  594. else:
  595. self.run()
  596. def send(self, msg):
  597. msg = msg.rstrip()
  598. msg_encoded = bytes(msg+"\r\n", "utf-8")
  599. try:
  600. lcchat.info("Client: "+msg)
  601. self.socket.send(msg_encoded)
  602. except Exception as e:
  603. self.is_connected = False
  604. lclient.error(e, exc_info=True)
  605. self.disconnect()
  606. # self.exit_event = True
  607. # self.status = "shutdown"
  608. def setup(self):
  609. pass
  610. def stop(self,reason=None):
  611. self.disconnect()
  612. self.exit_event = True
  613. if reason:
  614. print(reason)
  615. #