Statistics
| Branch: | Tag: | Revision:

root / lib / http / client.py @ 60452edf

History | View | Annotate | Download (12.5 kB)

1 02cab3e7 Michael Hanselmann
#
2 02cab3e7 Michael Hanselmann
#
3 02cab3e7 Michael Hanselmann
4 33231500 Michael Hanselmann
# Copyright (C) 2007, 2008, 2010 Google Inc.
5 02cab3e7 Michael Hanselmann
#
6 02cab3e7 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 02cab3e7 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 02cab3e7 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 02cab3e7 Michael Hanselmann
# (at your option) any later version.
10 02cab3e7 Michael Hanselmann
#
11 02cab3e7 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 02cab3e7 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 02cab3e7 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 02cab3e7 Michael Hanselmann
# General Public License for more details.
15 02cab3e7 Michael Hanselmann
#
16 02cab3e7 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 02cab3e7 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 02cab3e7 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02cab3e7 Michael Hanselmann
# 02110-1301, USA.
20 02cab3e7 Michael Hanselmann
21 02cab3e7 Michael Hanselmann
"""HTTP client module.
22 02cab3e7 Michael Hanselmann

23 02cab3e7 Michael Hanselmann
"""
24 02cab3e7 Michael Hanselmann
25 33231500 Michael Hanselmann
import logging
26 33231500 Michael Hanselmann
import pycurl
27 33231500 Michael Hanselmann
from cStringIO import StringIO
28 6c881c52 Iustin Pop
29 02cab3e7 Michael Hanselmann
from ganeti import http
30 33231500 Michael Hanselmann
from ganeti import compat
31 1a8337f2 Manuel Franceschini
from ganeti import netutils
32 02cab3e7 Michael Hanselmann
33 02cab3e7 Michael Hanselmann
34 02cab3e7 Michael Hanselmann
class HttpClientRequest(object):
35 02cab3e7 Michael Hanselmann
  def __init__(self, host, port, method, path, headers=None, post_data=None,
36 33231500 Michael Hanselmann
               read_timeout=None, curl_config_fn=None):
37 02cab3e7 Michael Hanselmann
    """Describes an HTTP request.
38 02cab3e7 Michael Hanselmann

39 02cab3e7 Michael Hanselmann
    @type host: string
40 02cab3e7 Michael Hanselmann
    @param host: Hostname
41 02cab3e7 Michael Hanselmann
    @type port: int
42 02cab3e7 Michael Hanselmann
    @param port: Port
43 02cab3e7 Michael Hanselmann
    @type method: string
44 02cab3e7 Michael Hanselmann
    @param method: Method name
45 02cab3e7 Michael Hanselmann
    @type path: string
46 02cab3e7 Michael Hanselmann
    @param path: Request path
47 33231500 Michael Hanselmann
    @type headers: list or None
48 33231500 Michael Hanselmann
    @param headers: Additional headers to send, list of strings
49 02cab3e7 Michael Hanselmann
    @type post_data: string or None
50 02cab3e7 Michael Hanselmann
    @param post_data: Additional data to send
51 e0036155 Iustin Pop
    @type read_timeout: int
52 e0036155 Iustin Pop
    @param read_timeout: if passed, it will be used as the read
53 e0036155 Iustin Pop
        timeout while reading the response from the server
54 33231500 Michael Hanselmann
    @type curl_config_fn: callable
55 33231500 Michael Hanselmann
    @param curl_config_fn: Function to configure cURL object before request
56 33231500 Michael Hanselmann
                           (Note: if the function configures the connection in
57 33231500 Michael Hanselmann
                           a way where it wouldn't be efficient to reuse them,
58 33231500 Michael Hanselmann
                           a "identity" property should be defined, see
59 33231500 Michael Hanselmann
                           L{HttpClientRequest.identity})
60 02cab3e7 Michael Hanselmann

61 02cab3e7 Michael Hanselmann
    """
62 02cab3e7 Michael Hanselmann
    assert path.startswith("/"), "Path must start with slash (/)"
63 33231500 Michael Hanselmann
    assert curl_config_fn is None or callable(curl_config_fn)
64 02cab3e7 Michael Hanselmann
65 02cab3e7 Michael Hanselmann
    # Request attributes
66 02cab3e7 Michael Hanselmann
    self.host = host
67 02cab3e7 Michael Hanselmann
    self.port = port
68 02cab3e7 Michael Hanselmann
    self.method = method
69 02cab3e7 Michael Hanselmann
    self.path = path
70 e0036155 Iustin Pop
    self.read_timeout = read_timeout
71 33231500 Michael Hanselmann
    self.curl_config_fn = curl_config_fn
72 02cab3e7 Michael Hanselmann
73 33231500 Michael Hanselmann
    if post_data is None:
74 33231500 Michael Hanselmann
      self.post_data = ""
75 33231500 Michael Hanselmann
    else:
76 33231500 Michael Hanselmann
      self.post_data = post_data
77 33231500 Michael Hanselmann
78 33231500 Michael Hanselmann
    if headers is None:
79 33231500 Michael Hanselmann
      self.headers = []
80 33231500 Michael Hanselmann
    elif isinstance(headers, dict):
81 33231500 Michael Hanselmann
      # Support for old interface
82 33231500 Michael Hanselmann
      self.headers = ["%s: %s" % (name, value)
83 33231500 Michael Hanselmann
                      for name, value in headers.items()]
84 33231500 Michael Hanselmann
    else:
85 33231500 Michael Hanselmann
      self.headers = headers
86 33231500 Michael Hanselmann
87 33231500 Michael Hanselmann
    # Response status
88 02cab3e7 Michael Hanselmann
    self.success = None
89 02cab3e7 Michael Hanselmann
    self.error = None
90 02cab3e7 Michael Hanselmann
91 02cab3e7 Michael Hanselmann
    # Response attributes
92 02cab3e7 Michael Hanselmann
    self.resp_status_code = None
93 02cab3e7 Michael Hanselmann
    self.resp_body = None
94 02cab3e7 Michael Hanselmann
95 9fa2e150 Michael Hanselmann
  def __repr__(self):
96 9fa2e150 Michael Hanselmann
    status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__),
97 9fa2e150 Michael Hanselmann
              "%s:%s" % (self.host, self.port),
98 9fa2e150 Michael Hanselmann
              self.method,
99 9fa2e150 Michael Hanselmann
              self.path]
100 9fa2e150 Michael Hanselmann
101 9fa2e150 Michael Hanselmann
    return "<%s at %#x>" % (" ".join(status), id(self))
102 9fa2e150 Michael Hanselmann
103 33231500 Michael Hanselmann
  @property
104 33231500 Michael Hanselmann
  def url(self):
105 33231500 Michael Hanselmann
    """Returns the full URL for this requests.
106 02cab3e7 Michael Hanselmann

107 33231500 Michael Hanselmann
    """
108 1a8337f2 Manuel Franceschini
    if netutils.IPAddress.IsValid(self.host):
109 981732fb Manuel Franceschini
      address = netutils.FormatAddress((self.host, self.port))
110 1a8337f2 Manuel Franceschini
    else:
111 1a8337f2 Manuel Franceschini
      address = "%s:%s" % (self.host, self.port)
112 33231500 Michael Hanselmann
    # TODO: Support for non-SSL requests
113 1a8337f2 Manuel Franceschini
    return "https://%s%s" % (address, self.path)
114 02cab3e7 Michael Hanselmann
115 33231500 Michael Hanselmann
  @property
116 33231500 Michael Hanselmann
  def identity(self):
117 33231500 Michael Hanselmann
    """Returns identifier for retrieving a pooled connection for this request.
118 02cab3e7 Michael Hanselmann

119 33231500 Michael Hanselmann
    This allows cURL client objects to be re-used and to cache information
120 33231500 Michael Hanselmann
    (e.g. SSL session IDs or connections).
121 02cab3e7 Michael Hanselmann

122 02cab3e7 Michael Hanselmann
    """
123 33231500 Michael Hanselmann
    parts = [self.host, self.port]
124 02cab3e7 Michael Hanselmann
125 33231500 Michael Hanselmann
    if self.curl_config_fn:
126 02cab3e7 Michael Hanselmann
      try:
127 33231500 Michael Hanselmann
        parts.append(self.curl_config_fn.identity)
128 33231500 Michael Hanselmann
      except AttributeError:
129 33231500 Michael Hanselmann
        pass
130 02cab3e7 Michael Hanselmann
131 33231500 Michael Hanselmann
    return "/".join(str(i) for i in parts)
132 02cab3e7 Michael Hanselmann
133 02cab3e7 Michael Hanselmann
134 33231500 Michael Hanselmann
class _HttpClient(object):
135 33231500 Michael Hanselmann
  def __init__(self, curl_config_fn):
136 33231500 Michael Hanselmann
    """Initializes this class.
137 02cab3e7 Michael Hanselmann

138 33231500 Michael Hanselmann
    @type curl_config_fn: callable
139 33231500 Michael Hanselmann
    @param curl_config_fn: Function to configure cURL object after
140 33231500 Michael Hanselmann
                           initialization
141 02cab3e7 Michael Hanselmann

142 33231500 Michael Hanselmann
    """
143 33231500 Michael Hanselmann
    self._req = None
144 02cab3e7 Michael Hanselmann
145 33231500 Michael Hanselmann
    curl = self._CreateCurlHandle()
146 33231500 Michael Hanselmann
    curl.setopt(pycurl.VERBOSE, False)
147 33231500 Michael Hanselmann
    curl.setopt(pycurl.NOSIGNAL, True)
148 33231500 Michael Hanselmann
    curl.setopt(pycurl.USERAGENT, http.HTTP_GANETI_VERSION)
149 33231500 Michael Hanselmann
    curl.setopt(pycurl.PROXY, "")
150 02cab3e7 Michael Hanselmann
151 7b70d7a8 Apollon Oikonomopoulos
    # Disable SSL session ID caching (pycurl >= 7.16.0)
152 7b70d7a8 Apollon Oikonomopoulos
    if hasattr(pycurl, "SSL_SESSIONID_CACHE"):
153 7b70d7a8 Apollon Oikonomopoulos
      curl.setopt(pycurl.SSL_SESSIONID_CACHE, False)
154 7b70d7a8 Apollon Oikonomopoulos
155 33231500 Michael Hanselmann
    # Pass cURL object to external config function
156 33231500 Michael Hanselmann
    if curl_config_fn:
157 33231500 Michael Hanselmann
      curl_config_fn(curl)
158 02cab3e7 Michael Hanselmann
159 33231500 Michael Hanselmann
    self._curl = curl
160 02cab3e7 Michael Hanselmann
161 33231500 Michael Hanselmann
  @staticmethod
162 33231500 Michael Hanselmann
  def _CreateCurlHandle():
163 33231500 Michael Hanselmann
    """Returns a new cURL object.
164 02cab3e7 Michael Hanselmann

165 02cab3e7 Michael Hanselmann
    """
166 33231500 Michael Hanselmann
    return pycurl.Curl()
167 02cab3e7 Michael Hanselmann
168 33231500 Michael Hanselmann
  def GetCurlHandle(self):
169 33231500 Michael Hanselmann
    """Returns the cURL object.
170 02cab3e7 Michael Hanselmann

171 33231500 Michael Hanselmann
    """
172 33231500 Michael Hanselmann
    return self._curl
173 02cab3e7 Michael Hanselmann
174 33231500 Michael Hanselmann
  def GetCurrentRequest(self):
175 33231500 Michael Hanselmann
    """Returns the current request.
176 02cab3e7 Michael Hanselmann

177 33231500 Michael Hanselmann
    @rtype: L{HttpClientRequest} or None
178 02cab3e7 Michael Hanselmann

179 33231500 Michael Hanselmann
    """
180 33231500 Michael Hanselmann
    return self._req
181 02cab3e7 Michael Hanselmann
182 33231500 Michael Hanselmann
  def StartRequest(self, req):
183 33231500 Michael Hanselmann
    """Starts a request on this client.
184 02cab3e7 Michael Hanselmann

185 33231500 Michael Hanselmann
    @type req: L{HttpClientRequest}
186 33231500 Michael Hanselmann
    @param req: HTTP request
187 02cab3e7 Michael Hanselmann

188 33231500 Michael Hanselmann
    """
189 33231500 Michael Hanselmann
    assert not self._req, "Another request is already started"
190 33231500 Michael Hanselmann
191 33231500 Michael Hanselmann
    self._req = req
192 33231500 Michael Hanselmann
    self._resp_buffer = StringIO()
193 33231500 Michael Hanselmann
194 33231500 Michael Hanselmann
    url = req.url
195 33231500 Michael Hanselmann
    method = req.method
196 33231500 Michael Hanselmann
    post_data = req.post_data
197 33231500 Michael Hanselmann
    headers = req.headers
198 33231500 Michael Hanselmann
199 33231500 Michael Hanselmann
    # PycURL requires strings to be non-unicode
200 33231500 Michael Hanselmann
    assert isinstance(method, str)
201 33231500 Michael Hanselmann
    assert isinstance(url, str)
202 33231500 Michael Hanselmann
    assert isinstance(post_data, str)
203 33231500 Michael Hanselmann
    assert compat.all(isinstance(i, str) for i in headers)
204 33231500 Michael Hanselmann
205 33231500 Michael Hanselmann
    # Configure cURL object for request
206 33231500 Michael Hanselmann
    curl = self._curl
207 33231500 Michael Hanselmann
    curl.setopt(pycurl.CUSTOMREQUEST, str(method))
208 33231500 Michael Hanselmann
    curl.setopt(pycurl.URL, url)
209 33231500 Michael Hanselmann
    curl.setopt(pycurl.POSTFIELDS, post_data)
210 33231500 Michael Hanselmann
    curl.setopt(pycurl.WRITEFUNCTION, self._resp_buffer.write)
211 33231500 Michael Hanselmann
    curl.setopt(pycurl.HTTPHEADER, headers)
212 33231500 Michael Hanselmann
213 33231500 Michael Hanselmann
    if req.read_timeout is None:
214 33231500 Michael Hanselmann
      curl.setopt(pycurl.TIMEOUT, 0)
215 33231500 Michael Hanselmann
    else:
216 33231500 Michael Hanselmann
      curl.setopt(pycurl.TIMEOUT, int(req.read_timeout))
217 02cab3e7 Michael Hanselmann
218 33231500 Michael Hanselmann
    # Pass cURL object to external config function
219 33231500 Michael Hanselmann
    if req.curl_config_fn:
220 33231500 Michael Hanselmann
      req.curl_config_fn(curl)
221 02cab3e7 Michael Hanselmann
222 33231500 Michael Hanselmann
  def Done(self, errmsg):
223 33231500 Michael Hanselmann
    """Finishes a request.
224 02cab3e7 Michael Hanselmann

225 33231500 Michael Hanselmann
    @type errmsg: string or None
226 33231500 Michael Hanselmann
    @param errmsg: Error message if request failed
227 02cab3e7 Michael Hanselmann

228 02cab3e7 Michael Hanselmann
    """
229 33231500 Michael Hanselmann
    req = self._req
230 33231500 Michael Hanselmann
    assert req, "No request"
231 02cab3e7 Michael Hanselmann
232 33231500 Michael Hanselmann
    logging.debug("Request %s finished, errmsg=%s", req, errmsg)
233 02cab3e7 Michael Hanselmann
234 33231500 Michael Hanselmann
    curl = self._curl
235 02cab3e7 Michael Hanselmann
236 33231500 Michael Hanselmann
    req.success = not bool(errmsg)
237 33231500 Michael Hanselmann
    req.error = errmsg
238 02cab3e7 Michael Hanselmann
239 33231500 Michael Hanselmann
    # Get HTTP response code
240 33231500 Michael Hanselmann
    req.resp_status_code = curl.getinfo(pycurl.RESPONSE_CODE)
241 33231500 Michael Hanselmann
    req.resp_body = self._resp_buffer.getvalue()
242 02cab3e7 Michael Hanselmann
243 33231500 Michael Hanselmann
    # Reset client object
244 33231500 Michael Hanselmann
    self._req = None
245 33231500 Michael Hanselmann
    self._resp_buffer = None
246 02cab3e7 Michael Hanselmann
247 33231500 Michael Hanselmann
    # Ensure no potentially large variables are referenced
248 33231500 Michael Hanselmann
    curl.setopt(pycurl.POSTFIELDS, "")
249 33231500 Michael Hanselmann
    curl.setopt(pycurl.WRITEFUNCTION, lambda _: None)
250 02cab3e7 Michael Hanselmann
251 02cab3e7 Michael Hanselmann
252 33231500 Michael Hanselmann
class _PooledHttpClient:
253 33231500 Michael Hanselmann
  """Data structure for HTTP client pool.
254 02cab3e7 Michael Hanselmann

255 33231500 Michael Hanselmann
  """
256 33231500 Michael Hanselmann
  def __init__(self, identity, client):
257 33231500 Michael Hanselmann
    """Initializes this class.
258 f2e13d55 Michael Hanselmann

259 33231500 Michael Hanselmann
    @type identity: string
260 33231500 Michael Hanselmann
    @param identity: Client identifier for pool
261 33231500 Michael Hanselmann
    @type client: L{_HttpClient}
262 33231500 Michael Hanselmann
    @param client: HTTP client
263 02cab3e7 Michael Hanselmann

264 02cab3e7 Michael Hanselmann
    """
265 33231500 Michael Hanselmann
    self.identity = identity
266 33231500 Michael Hanselmann
    self.client = client
267 33231500 Michael Hanselmann
    self.lastused = 0
268 02cab3e7 Michael Hanselmann
269 33231500 Michael Hanselmann
  def __repr__(self):
270 33231500 Michael Hanselmann
    status = ["%s.%s" % (self.__class__.__module__, self.__class__.__name__),
271 33231500 Michael Hanselmann
              "id=%s" % self.identity,
272 33231500 Michael Hanselmann
              "lastuse=%s" % self.lastused,
273 33231500 Michael Hanselmann
              repr(self.client)]
274 02cab3e7 Michael Hanselmann
275 33231500 Michael Hanselmann
    return "<%s at %#x>" % (" ".join(status), id(self))
276 02cab3e7 Michael Hanselmann
277 02cab3e7 Michael Hanselmann
278 33231500 Michael Hanselmann
class HttpClientPool:
279 33231500 Michael Hanselmann
  """A simple HTTP client pool.
280 02cab3e7 Michael Hanselmann

281 33231500 Michael Hanselmann
  Supports one pooled connection per identity (see
282 33231500 Michael Hanselmann
  L{HttpClientRequest.identity}).
283 02cab3e7 Michael Hanselmann

284 33231500 Michael Hanselmann
  """
285 33231500 Michael Hanselmann
  #: After how many generations to drop unused clients
286 33231500 Michael Hanselmann
  _MAX_GENERATIONS_DROP = 25
287 33231500 Michael Hanselmann
288 33231500 Michael Hanselmann
  def __init__(self, curl_config_fn):
289 33231500 Michael Hanselmann
    """Initializes this class.
290 33231500 Michael Hanselmann

291 33231500 Michael Hanselmann
    @type curl_config_fn: callable
292 33231500 Michael Hanselmann
    @param curl_config_fn: Function to configure cURL object after
293 33231500 Michael Hanselmann
                           initialization
294 02cab3e7 Michael Hanselmann

295 02cab3e7 Michael Hanselmann
    """
296 33231500 Michael Hanselmann
    self._curl_config_fn = curl_config_fn
297 33231500 Michael Hanselmann
    self._generation = 0
298 33231500 Michael Hanselmann
    self._pool = {}
299 02cab3e7 Michael Hanselmann
300 33231500 Michael Hanselmann
  @staticmethod
301 33231500 Michael Hanselmann
  def _GetHttpClientCreator():
302 33231500 Michael Hanselmann
    """Returns callable to create HTTP client.
303 e0036155 Iustin Pop

304 33231500 Michael Hanselmann
    """
305 33231500 Michael Hanselmann
    return _HttpClient
306 02cab3e7 Michael Hanselmann
307 33231500 Michael Hanselmann
  def _Get(self, identity):
308 33231500 Michael Hanselmann
    """Gets an HTTP client from the pool.
309 02cab3e7 Michael Hanselmann

310 33231500 Michael Hanselmann
    @type identity: string
311 33231500 Michael Hanselmann
    @param identity: Client identifier
312 02cab3e7 Michael Hanselmann

313 33231500 Michael Hanselmann
    """
314 33231500 Michael Hanselmann
    try:
315 33231500 Michael Hanselmann
      pclient  = self._pool.pop(identity)
316 33231500 Michael Hanselmann
    except KeyError:
317 33231500 Michael Hanselmann
      # Need to create new client
318 33231500 Michael Hanselmann
      client = self._GetHttpClientCreator()(self._curl_config_fn)
319 33231500 Michael Hanselmann
      pclient = _PooledHttpClient(identity, client)
320 33231500 Michael Hanselmann
      logging.debug("Created new client %s", pclient)
321 33231500 Michael Hanselmann
    else:
322 33231500 Michael Hanselmann
      logging.debug("Reusing client %s", pclient)
323 02cab3e7 Michael Hanselmann
324 33231500 Michael Hanselmann
    assert pclient.identity == identity
325 02cab3e7 Michael Hanselmann
326 33231500 Michael Hanselmann
    return pclient
327 02cab3e7 Michael Hanselmann
328 33231500 Michael Hanselmann
  def _StartRequest(self, req):
329 33231500 Michael Hanselmann
    """Starts a request.
330 9fa2e150 Michael Hanselmann

331 33231500 Michael Hanselmann
    @type req: L{HttpClientRequest}
332 33231500 Michael Hanselmann
    @param req: HTTP request
333 9fa2e150 Michael Hanselmann

334 33231500 Michael Hanselmann
    """
335 33231500 Michael Hanselmann
    logging.debug("Starting request %r", req)
336 33231500 Michael Hanselmann
    pclient = self._Get(req.identity)
337 02cab3e7 Michael Hanselmann
338 33231500 Michael Hanselmann
    assert req.identity not in self._pool
339 02cab3e7 Michael Hanselmann
340 33231500 Michael Hanselmann
    pclient.client.StartRequest(req)
341 33231500 Michael Hanselmann
    pclient.lastused = self._generation
342 33231500 Michael Hanselmann
343 33231500 Michael Hanselmann
    return pclient
344 02cab3e7 Michael Hanselmann
345 33231500 Michael Hanselmann
  def _Return(self, pclients):
346 33231500 Michael Hanselmann
    """Returns HTTP clients to the pool.
347 02cab3e7 Michael Hanselmann

348 33231500 Michael Hanselmann
    """
349 33231500 Michael Hanselmann
    for pc in pclients:
350 33231500 Michael Hanselmann
      logging.debug("Returning client %s to pool", pc)
351 33231500 Michael Hanselmann
      assert pc.identity not in self._pool
352 33231500 Michael Hanselmann
      assert pc not in self._pool.values()
353 33231500 Michael Hanselmann
      self._pool[pc.identity] = pc
354 33231500 Michael Hanselmann
355 33231500 Michael Hanselmann
    # Check for unused clients
356 33231500 Michael Hanselmann
    for pc in self._pool.values():
357 33231500 Michael Hanselmann
      if (pc.lastused + self._MAX_GENERATIONS_DROP) < self._generation:
358 33231500 Michael Hanselmann
        logging.debug("Removing client %s which hasn't been used"
359 33231500 Michael Hanselmann
                      " for %s generations",
360 33231500 Michael Hanselmann
                      pc, self._MAX_GENERATIONS_DROP)
361 33231500 Michael Hanselmann
        self._pool.pop(pc.identity, None)
362 33231500 Michael Hanselmann
363 33231500 Michael Hanselmann
    assert compat.all(pc.lastused >= (self._generation -
364 33231500 Michael Hanselmann
                                      self._MAX_GENERATIONS_DROP)
365 33231500 Michael Hanselmann
                      for pc in self._pool.values())
366 33231500 Michael Hanselmann
367 33231500 Michael Hanselmann
  @staticmethod
368 33231500 Michael Hanselmann
  def _CreateCurlMultiHandle():
369 33231500 Michael Hanselmann
    """Creates new cURL multi handle.
370 02cab3e7 Michael Hanselmann

371 33231500 Michael Hanselmann
    """
372 33231500 Michael Hanselmann
    return pycurl.CurlMulti()
373 02cab3e7 Michael Hanselmann
374 33231500 Michael Hanselmann
  def ProcessRequests(self, requests):
375 33231500 Michael Hanselmann
    """Processes any number of HTTP client requests using pooled objects.
376 02cab3e7 Michael Hanselmann

377 33231500 Michael Hanselmann
    @type requests: list of L{HttpClientRequest}
378 33231500 Michael Hanselmann
    @param requests: List of all requests
379 02cab3e7 Michael Hanselmann

380 33231500 Michael Hanselmann
    """
381 33231500 Michael Hanselmann
    multi = self._CreateCurlMultiHandle()
382 02cab3e7 Michael Hanselmann
383 33231500 Michael Hanselmann
    # For client cleanup
384 33231500 Michael Hanselmann
    self._generation += 1
385 02cab3e7 Michael Hanselmann
386 33231500 Michael Hanselmann
    assert compat.all((req.error is None and
387 33231500 Michael Hanselmann
                       req.success is None and
388 33231500 Michael Hanselmann
                       req.resp_status_code is None and
389 33231500 Michael Hanselmann
                       req.resp_body is None)
390 33231500 Michael Hanselmann
                      for req in requests)
391 02cab3e7 Michael Hanselmann
392 33231500 Michael Hanselmann
    curl_to_pclient = {}
393 33231500 Michael Hanselmann
    for req in requests:
394 33231500 Michael Hanselmann
      pclient = self._StartRequest(req)
395 33231500 Michael Hanselmann
      curl = pclient.client.GetCurlHandle()
396 33231500 Michael Hanselmann
      curl_to_pclient[curl] = pclient
397 33231500 Michael Hanselmann
      multi.add_handle(curl)
398 33231500 Michael Hanselmann
      assert pclient.client.GetCurrentRequest() == req
399 33231500 Michael Hanselmann
      assert pclient.lastused >= 0
400 02cab3e7 Michael Hanselmann
401 33231500 Michael Hanselmann
    assert len(curl_to_pclient) == len(requests)
402 02cab3e7 Michael Hanselmann
403 33231500 Michael Hanselmann
    done_count = 0
404 33231500 Michael Hanselmann
    while True:
405 33231500 Michael Hanselmann
      (ret, _) = multi.perform()
406 33231500 Michael Hanselmann
      assert ret in (pycurl.E_MULTI_OK, pycurl.E_CALL_MULTI_PERFORM)
407 33231500 Michael Hanselmann
408 33231500 Michael Hanselmann
      if ret == pycurl.E_CALL_MULTI_PERFORM:
409 33231500 Michael Hanselmann
        # cURL wants to be called again
410 33231500 Michael Hanselmann
        continue
411 33231500 Michael Hanselmann
412 33231500 Michael Hanselmann
      while True:
413 33231500 Michael Hanselmann
        (remaining_messages, successful, failed) = multi.info_read()
414 33231500 Michael Hanselmann
415 33231500 Michael Hanselmann
        for curl in successful:
416 33231500 Michael Hanselmann
          multi.remove_handle(curl)
417 33231500 Michael Hanselmann
          done_count += 1
418 33231500 Michael Hanselmann
          pclient = curl_to_pclient[curl]
419 33231500 Michael Hanselmann
          req = pclient.client.GetCurrentRequest()
420 33231500 Michael Hanselmann
          pclient.client.Done(None)
421 33231500 Michael Hanselmann
          assert req.success
422 33231500 Michael Hanselmann
          assert not pclient.client.GetCurrentRequest()
423 33231500 Michael Hanselmann
424 33231500 Michael Hanselmann
        for curl, errnum, errmsg in failed:
425 33231500 Michael Hanselmann
          multi.remove_handle(curl)
426 33231500 Michael Hanselmann
          done_count += 1
427 33231500 Michael Hanselmann
          pclient = curl_to_pclient[curl]
428 33231500 Michael Hanselmann
          req = pclient.client.GetCurrentRequest()
429 33231500 Michael Hanselmann
          pclient.client.Done("Error %s: %s" % (errnum, errmsg))
430 33231500 Michael Hanselmann
          assert req.error
431 33231500 Michael Hanselmann
          assert not pclient.client.GetCurrentRequest()
432 33231500 Michael Hanselmann
433 33231500 Michael Hanselmann
        if remaining_messages == 0:
434 33231500 Michael Hanselmann
          break
435 33231500 Michael Hanselmann
436 33231500 Michael Hanselmann
      assert done_count <= len(requests)
437 33231500 Michael Hanselmann
438 33231500 Michael Hanselmann
      if done_count == len(requests):
439 33231500 Michael Hanselmann
        break
440 02cab3e7 Michael Hanselmann
441 33231500 Michael Hanselmann
      # Wait for I/O. The I/O timeout shouldn't be too long so that HTTP
442 33231500 Michael Hanselmann
      # timeouts, which are only evaluated in multi.perform, aren't
443 33231500 Michael Hanselmann
      # unnecessarily delayed.
444 33231500 Michael Hanselmann
      multi.select(1.0)
445 02cab3e7 Michael Hanselmann
446 33231500 Michael Hanselmann
    assert compat.all(pclient.client.GetCurrentRequest() is None
447 33231500 Michael Hanselmann
                      for pclient in curl_to_pclient.values())
448 02cab3e7 Michael Hanselmann
449 33231500 Michael Hanselmann
    # Return clients to pool
450 33231500 Michael Hanselmann
    self._Return(curl_to_pclient.values())
451 02cab3e7 Michael Hanselmann
452 33231500 Michael Hanselmann
    assert done_count == len(requests)
453 33231500 Michael Hanselmann
    assert compat.all(req.error is not None or
454 33231500 Michael Hanselmann
                      (req.success and
455 33231500 Michael Hanselmann
                       req.resp_status_code is not None and
456 33231500 Michael Hanselmann
                       req.resp_body is not None)
457 33231500 Michael Hanselmann
                      for req in requests)