CMS 3D CMS Logo

HTTP.py
Go to the documentation of this file.
1 from builtins import range
2 from io import StringIO
3 from io import BytesIO
4 from pycurl import *
5 
7  """Manager of multiple concurrent or overlapping HTTP requests.
8 
9 This is a utility class acting as a pump of several overlapping
10 HTTP requests against any number of HTTP or HTTPS servers. It
11 uses a configurable number of simultaneous connections, ten by
12 default. The actual connection layer is handled using curl, and
13 the client classes need to aware of this to a limited degree.
14 
15 The client supplies optional callback methods for initialising,
16 responding and handling errors on connections. At the very least
17 the request response callback should be defined.
18 
19 This class is not designed for multi-threaded use. It employs
20 overlapping requests, but in a single thread. Only one thread
21 at a time should be calling `process()`; several threads may
22 call `.put()` provided the caller uses a mutex so that only one
23 thread calls into the method at a time."""
24 
25  def __init__(self, num_connections = 10, ssl_opts = None,
26  user_agent = None, request_headers = None,
27  request_init = None, request_respond = None,
28  request_error = None, handle_init = None):
29  """Initialise the request manager. The arguments are:
30 
31 :arg num_connections: maximum number of simultaneous connections.
32 :arg ssl_opts: optional SSLOptions (Monitoring.Core.X509) for SSL
33 X509 parametre values, e.g. for X509 client authentication.
34 :arg user_agent: sets user agent identification string if defined.
35 :arg request_headers: if defined, specifies list of additional HTTP
36 request headers to be added to each request.
37 :arg request_init: optional callback to initialise requests; the
38 default assumes each task is a URL to access and sets the `URL`
39 property on the curl object to the task value.
40 :arg request_respond: callback for handling responses; at the very
41 minimum this should be defined as the default one does nothing.
42 :arg request_error: callback for handling connection errors; the
43 default one raises a RuntimeException.
44 :arg handle_init: callback for customising connection handles at
45 creation time; the callback will be invoked for each connection
46 object as it's created and queued to the idle connection list."""
47  self.request_respond = request_respond or self._request_respond
48  self.request_error = request_error or self._request_error
49  self.request_init = request_init or self._request_init
50  self.cm = CurlMulti()
51  self.handles = [Curl() for i in range(0, num_connections)]
52  self.free = [c for c in self.handles]
53  self.queue = []
54 
55  for c in self.handles:
56  c.buffer = None
57  c.setopt(NOSIGNAL, 1)
58  c.setopt(TIMEOUT, 300)
59  c.setopt(CONNECTTIMEOUT, 30)
60  c.setopt(FOLLOWLOCATION, 1)
61  c.setopt(MAXREDIRS, 5)
62  if user_agent:
63  c.setopt(USERAGENT, user_agent)
64  if ssl_opts:
65  c.setopt(CAPATH, ssl_opts.ca_path)
66  c.setopt(SSLCERT, ssl_opts.cert_file)
67  c.setopt(SSLKEY, ssl_opts.key_file)
68  if ssl_opts.key_pass:
69  c.setopt(SSLKEYPASSWD, ssl_opts.key_pass)
70  if request_headers:
71  c.setopt(HTTPHEADER, request_headers)
72  if handle_init:
73  handle_init(c)
74 
75  def _request_init(self, c, url):
76  """Default request initialisation callback."""
77  c.setopt(URL, url)
78 
79  def _request_error(self, c, task, errmsg, errno):
80  """Default request error callback."""
81  raise RuntimeError((task, errmsg, errno))
82 
83  def _request_respond(self, *args):
84  """Default request response callback."""
85  pass
86 
87  def put(self, task):
88  """Add a new task. The task object should be a tuple and is
89 passed to ``request_init`` callback passed to the constructor."""
90  self.queue.append(task)
91 
92  def process(self):
93  """Process pending requests until none are left.
94 
95 This method processes all requests queued with `.put()` until they
96 have been fully processed. It calls the ``request_respond`` callback
97 for all successfully completed requests, and ``request_error`` for
98 all failed ones.
99 
100 Any new requests added by callbacks by invoking ``put()`` are also
101 processed before returning."""
102  npending = 0
103  while self.queue or npending:
104  while self.queue and self.free:
105  c = self.free.pop()
106  c.task = self.queue.pop(0)
107  c.buffer = b = BytesIO()
108  c.setopt(WRITEFUNCTION, b.write)
109  self.request_init(c, *c.task)
110  self.cm.add_handle(c)
111  npending += 1
112 
113  while True:
114  ret, nhandles = self.cm.perform()
115  if ret != E_CALL_MULTI_PERFORM:
116  break
117 
118  while True:
119  numq, ok, err = self.cm.info_read()
120 
121  for c in ok:
122  assert npending > 0
123  self.cm.remove_handle(c)
124  self.request_respond(c)
125  c.buffer = None
126  self.free.append(c)
127  npending -= 1
128 
129  for c, errno, errmsg in err:
130  assert npending > 0
131  self.cm.remove_handle(c)
132  self.free.append(c)
133  npending -= 1
134  self.request_error(c, c.task, errmsg, errno)
135 
136  if numq == 0:
137  break
138 
139  self.cm.select(1.)
140 
def _request_init(self, c, url)
Definition: HTTP.py:75
def _request_error(self, c, task, errmsg, errno)
Definition: HTTP.py:79
def put(self, task)
Definition: HTTP.py:87
def process(self)
Definition: HTTP.py:92
def _request_respond(self, args)
Definition: HTTP.py:83
def __init__(self, num_connections=10, ssl_opts=None, user_agent=None, request_headers=None, request_init=None, request_respond=None, request_error=None, handle_init=None)
Definition: HTTP.py:28