|
6 | 6 | import sys |
7 | 7 | import os |
8 | 8 | import socket |
| 9 | +import select |
9 | 10 | try: |
10 | 11 | import urllib.request as urllib2 |
11 | 12 | except ImportError: |
@@ -479,92 +480,142 @@ def __init__(self, configuration=None, name=None): |
479 | 480 | self.port = None |
480 | 481 | self.unix_socket = None |
481 | 482 | self.request = "" |
| 483 | + self.__socket_config = None |
482 | 484 | SimpleService.__init__(self, configuration=configuration, name=name) |
483 | 485 |
|
484 | | - def _get_raw_data(self): |
| 486 | + def _connect(self): |
485 | 487 | """ |
486 | | - Get raw data with low-level "socket" module. |
487 | | - :return: str |
| 488 | + Recreate socket and connect to it since they cannot be reused |
| 489 | + Available configurations are IPv6, IPv4 or UNIX socket |
| 490 | + :return: |
488 | 491 | """ |
489 | | - # Recreate socket since they cannot be reused |
490 | | - if self._sock is None: |
491 | | - try: |
492 | | - if self.unix_socket is None: |
493 | | - self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
494 | | - self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
495 | | - #sock.setsockopt(socket.SOL_SOCKET, socket.TCP_NODELAY, 1) |
496 | | - #sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) |
497 | | - #sock.settimeout(self.update_every) |
498 | | - self._sock.settimeout(0.5) |
499 | | - self._sock.connect((self.host, self.port)) |
500 | | - self._sock.settimeout(0.5) # Just to be sure |
| 492 | + try: |
| 493 | + if self.unix_socket is None: |
| 494 | + if self.__socket_config is None: |
| 495 | + # establish ipv6 or ipv4 connection. |
| 496 | + for res in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM): |
| 497 | + try: |
| 498 | + af, socktype, proto, canonname, sa = res |
| 499 | + self._sock = socket.socket(af, socktype, proto) |
| 500 | + except socket.error as msg: |
| 501 | + self._sock = None |
| 502 | + continue |
| 503 | + try: |
| 504 | + self._sock.connect(sa) |
| 505 | + except socket.error as msg: |
| 506 | + self._disconnect() |
| 507 | + continue |
| 508 | + self.__socket_config = res |
| 509 | + break |
501 | 510 | else: |
502 | | - self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) |
503 | | - #self._sock.settimeout(self.update_every) |
504 | | - self._sock.settimeout(0.05) |
505 | | - self._sock.connect(self.unix_socket) |
506 | | - self._sock.settimeout(0.05) # Just to be sure |
507 | | - except Exception as e: |
508 | | - self.error(str(e), |
509 | | - "Cannot create socket with following configuration: host:", str(self.host), |
510 | | - "port:", str(self.port), |
511 | | - "socket:", str(self.unix_socket)) |
512 | | - self._sock = None |
513 | | - return None |
| 511 | + # connect to socket with previously established configuration |
| 512 | + try: |
| 513 | + af, socktype, proto, canonname, sa = self.__socket_config |
| 514 | + self._sock = socket.socket(af, socktype, proto) |
| 515 | + self._sock.connect(sa) |
| 516 | + except socket.error as msg: |
| 517 | + self._disconnect() |
| 518 | + else: |
| 519 | + # connect to unix socket |
| 520 | + self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) |
| 521 | + self._sock.connect(self.unix_socket) |
| 522 | + except Exception as e: |
| 523 | + self.error(str(e), |
| 524 | + "Cannot create socket with following configuration: host:", str(self.host), |
| 525 | + "port:", str(self.port), |
| 526 | + "socket:", str(self.unix_socket)) |
| 527 | + self._sock = None |
| 528 | + self._sock.setblocking(0) |
| 529 | + |
| 530 | + def _disconnect(self): |
| 531 | + """ |
| 532 | + Close socket connection |
| 533 | + :return: |
| 534 | + """ |
| 535 | + try: |
| 536 | + self._sock.shutdown(2) # 0 - read, 1 - write, 2 - all |
| 537 | + self._sock.close() |
| 538 | + except Exception: |
| 539 | + pass |
| 540 | + self._sock = None |
514 | 541 |
|
| 542 | + def _send(self): |
| 543 | + """ |
| 544 | + Send request. |
| 545 | + :return: boolean |
| 546 | + """ |
515 | 547 | # Send request if it is needed |
516 | 548 | if self.request != "".encode(): |
517 | 549 | try: |
518 | 550 | self._sock.send(self.request) |
519 | 551 | except Exception as e: |
520 | | - try: |
521 | | - self._sock.shutdown(0) |
522 | | - self._sock.close() |
523 | | - self._sock = None |
524 | | - except: |
525 | | - pass |
| 552 | + self._disconnect() |
526 | 553 | self.error(str(e), |
527 | 554 | "used configuration: host:", str(self.host), |
528 | 555 | "port:", str(self.port), |
529 | 556 | "socket:", str(self.unix_socket)) |
530 | | - return None |
531 | | - |
532 | | - # Receive first two bytes from socket. This will test if there is anything to receive |
533 | | - size = 2 |
534 | | - try: |
535 | | - data = self._sock.recv(size).decode() |
536 | | - except Exception as e: |
537 | | - self.error(str(e), |
538 | | - "used configuration: host:", str(self.host), |
539 | | - "port:", str(self.port), |
540 | | - "socket:", str(self.unix_socket)) |
541 | | - self._sock.shutdown(0) |
542 | | - self._sock.close() |
543 | | - self._sock = None |
544 | | - return None |
| 557 | + return False |
| 558 | + return True |
545 | 559 |
|
546 | | - # Receive the rest |
| 560 | + def _receive(self): |
| 561 | + """ |
| 562 | + Receive data from socket |
| 563 | + :return: str |
| 564 | + """ |
| 565 | + data = "" |
547 | 566 | while True: |
548 | | - # implement something like TCP Window Scaling |
549 | | - if size < 4096: |
550 | | - size *= 2 |
551 | | - buf = self._sock.recv(size) |
552 | | - data += buf.decode() |
553 | | - if len(buf) == 0: break # precaution |
554 | | - if len(buf) < size and not self._more_data_available(): |
| 567 | + try: |
| 568 | + ready_to_read, _, in_error = select.select([self._sock], [], [], 0.01) |
| 569 | + except Exception as e: |
| 570 | + self.debug("SELECT", str(e)) |
| 571 | + self._disconnect() |
555 | 572 | break |
| 573 | + if len(ready_to_read) > 0: |
| 574 | + buf = self._sock.recv(4096) |
| 575 | + if len(buf) == 0 or buf is None: |
| 576 | + break |
| 577 | + data += buf.decode() |
556 | 578 | else: |
557 | | - size = 4 |
| 579 | + self._disconnect() |
| 580 | + break |
| 581 | + |
| 582 | + return data |
| 583 | + |
| 584 | + def _get_raw_data(self): |
| 585 | + """ |
| 586 | + Get raw data with low-level "socket" module. |
| 587 | + :return: str |
| 588 | + """ |
| 589 | + if self._sock is None: |
| 590 | + self._connect() |
| 591 | + |
| 592 | + # Send request if it is needed |
| 593 | + if not self._send(): |
| 594 | + return None |
| 595 | + |
| 596 | + finished = False |
| 597 | + data = "" |
| 598 | + prevent_infinite_loop = 1000000 |
| 599 | + while not finished: |
| 600 | + data += self._receive() |
| 601 | + finished = self._check_raw_data(data) |
| 602 | + prevent_infinite_loop -= 1 |
| 603 | + if prevent_infinite_loop <= 0: |
| 604 | + self.debug("Almost got into infinite loop while grabbing data. Is _check_raw_data() ok?") |
| 605 | + break |
558 | 606 |
|
559 | 607 | if not self._keep_alive: |
560 | | - self._sock.shutdown(0) |
561 | | - self._sock.close() |
562 | | - self._sock = None |
| 608 | + self._disconnect() |
563 | 609 |
|
564 | 610 | return data |
565 | 611 |
|
566 | | - def _more_data_available(self): |
567 | | - return False |
| 612 | + def _check_raw_data(self, data): |
| 613 | + """ |
| 614 | + Check if all data has been gathered from socket |
| 615 | + :param data: str |
| 616 | + :return: boolean |
| 617 | + """ |
| 618 | + return True |
568 | 619 |
|
569 | 620 | def _parse_config(self): |
570 | 621 | """ |
|
0 commit comments