18790: Merge branch 'main' into 18790-log-client
[arvados.git] / sdk / python / arvados / _pycurlhelper.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: Apache-2.0
4
5 import socket
6 import pycurl
7 import math
8
9 class PyCurlHelper:
10     # Default Keep server connection timeout:  2 seconds
11     # Default Keep server read timeout:       256 seconds
12     # Default Keep server bandwidth minimum:  32768 bytes per second
13     # Default Keep proxy connection timeout:  20 seconds
14     # Default Keep proxy read timeout:        256 seconds
15     # Default Keep proxy bandwidth minimum:   32768 bytes per second
16     DEFAULT_TIMEOUT = (2, 256, 32768)
17     DEFAULT_PROXY_TIMEOUT = (20, 256, 32768)
18
19     def __init__(self, title_case_headers=False):
20         self._socket = None
21         self.title_case_headers = title_case_headers
22
23     def _socket_open(self, *args, **kwargs):
24         if len(args) + len(kwargs) == 2:
25             return self._socket_open_pycurl_7_21_5(*args, **kwargs)
26         else:
27             return self._socket_open_pycurl_7_19_3(*args, **kwargs)
28
29     def _socket_open_pycurl_7_19_3(self, family, socktype, protocol, address=None):
30         return self._socket_open_pycurl_7_21_5(
31             purpose=None,
32             address=collections.namedtuple(
33                 'Address', ['family', 'socktype', 'protocol', 'addr'],
34             )(family, socktype, protocol, address))
35
36     def _socket_open_pycurl_7_21_5(self, purpose, address):
37         """Because pycurl doesn't have CURLOPT_TCP_KEEPALIVE"""
38         s = socket.socket(address.family, address.socktype, address.protocol)
39         s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
40         # Will throw invalid protocol error on mac. This test prevents that.
41         if hasattr(socket, 'TCP_KEEPIDLE'):
42             s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 75)
43         s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 75)
44         self._socket = s
45         return s
46
47     def _setcurltimeouts(self, curl, timeouts, ignore_bandwidth=False):
48         if not timeouts:
49             return
50         elif isinstance(timeouts, tuple):
51             if len(timeouts) == 2:
52                 conn_t, xfer_t = timeouts
53                 bandwidth_bps = self.DEFAULT_TIMEOUT[2]
54             else:
55                 conn_t, xfer_t, bandwidth_bps = timeouts
56         else:
57             conn_t, xfer_t = (timeouts, timeouts)
58             bandwidth_bps = self.DEFAULT_TIMEOUT[2]
59         curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(conn_t*1000))
60         if not ignore_bandwidth:
61             curl.setopt(pycurl.LOW_SPEED_TIME, int(math.ceil(xfer_t)))
62             curl.setopt(pycurl.LOW_SPEED_LIMIT, int(math.ceil(bandwidth_bps)))
63
64     def _headerfunction(self, header_line):
65         if isinstance(header_line, bytes):
66             header_line = header_line.decode('iso-8859-1')
67         if ':' in header_line:
68             name, value = header_line.split(':', 1)
69             if self.title_case_headers:
70                 name = name.strip().title()
71             else:
72                 name = name.strip().lower()
73             value = value.strip()
74         elif self._headers:
75             name = self._lastheadername
76             value = self._headers[name] + ' ' + header_line.strip()
77         elif header_line.startswith('HTTP/'):
78             name = 'x-status-line'
79             value = header_line
80         else:
81             _logger.error("Unexpected header line: %s", header_line)
82             return
83         self._lastheadername = name
84         self._headers[name] = value
85         # Returning None implies all bytes were written