Statistics
| Branch: | Tag: | Revision:

root / lib / http / client.py @ 35007011

History | View | Annotate | Download (12.7 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 85061b9a Michael Hanselmann
    logging.debug("Starting request %r", req)
192 85061b9a Michael Hanselmann
193 33231500 Michael Hanselmann
    self._req = req
194 33231500 Michael Hanselmann
    self._resp_buffer = StringIO()
195 33231500 Michael Hanselmann
196 33231500 Michael Hanselmann
    url = req.url
197 33231500 Michael Hanselmann
    method = req.method
198 33231500 Michael Hanselmann
    post_data = req.post_data
199 33231500 Michael Hanselmann
    headers = req.headers
200 33231500 Michael Hanselmann
201 33231500 Michael Hanselmann
    # PycURL requires strings to be non-unicode
202 33231500 Michael Hanselmann
    assert isinstance(method, str)
203 33231500 Michael Hanselmann
    assert isinstance(url, str)
204 33231500 Michael Hanselmann
    assert isinstance(post_data, str)
205 33231500 Michael Hanselmann
    assert compat.all(isinstance(i, str) for i in headers)
206 33231500 Michael Hanselmann
207 33231500 Michael Hanselmann
    # Configure cURL object for request
208 33231500 Michael Hanselmann
    curl = self._curl
209 33231500 Michael Hanselmann
    curl.setopt(pycurl.CUSTOMREQUEST, str(method))
210 33231500 Michael Hanselmann
    curl.setopt(pycurl.URL, url)
211 33231500 Michael Hanselmann
    curl.setopt(pycurl.POSTFIELDS, post_data)
212 33231500 Michael Hanselmann
    curl.setopt(pycurl.WRITEFUNCTION, self._resp_buffer.write)
213 33231500 Michael Hanselmann
    curl.setopt(pycurl.HTTPHEADER, headers)
214 33231500 Michael Hanselmann
215 33231500 Michael Hanselmann
    if req.read_timeout is None:
216 33231500 Michael Hanselmann
      curl.setopt(pycurl.TIMEOUT, 0)
217 33231500 Michael Hanselmann
    else:
218 33231500 Michael Hanselmann
      curl.setopt(pycurl.TIMEOUT, int(req.read_timeout))
219 02cab3e7 Michael Hanselmann
220 33231500 Michael Hanselmann
    # Pass cURL object to external config function
221 33231500 Michael Hanselmann
    if req.curl_config_fn:
222 33231500 Michael Hanselmann
      req.curl_config_fn(curl)
223 02cab3e7 Michael Hanselmann
224 33231500 Michael Hanselmann
  def Done(self, errmsg):
225 33231500 Michael Hanselmann
    """Finishes a request.
226 02cab3e7 Michael Hanselmann

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

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

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

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

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

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

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

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

297 02cab3e7 Michael Hanselmann
    """
298 33231500 Michael Hanselmann
    self._curl_config_fn = curl_config_fn
299 33231500 Michael Hanselmann
    self._generation = 0
300 33231500 Michael Hanselmann
    self._pool = {}
301 02cab3e7 Michael Hanselmann
302 85061b9a Michael Hanselmann
    # Create custom logger for HTTP client pool. Change logging level to
303 85061b9a Michael Hanselmann
    # C{logging.NOTSET} to get more details.
304 85061b9a Michael Hanselmann
    self._logger = logging.getLogger(self.__class__.__name__)
305 85061b9a Michael Hanselmann
    self._logger.setLevel(logging.INFO)
306 85061b9a Michael Hanselmann
307 33231500 Michael Hanselmann
  @staticmethod
308 33231500 Michael Hanselmann
  def _GetHttpClientCreator():
309 33231500 Michael Hanselmann
    """Returns callable to create HTTP client.
310 e0036155 Iustin Pop

311 33231500 Michael Hanselmann
    """
312 33231500 Michael Hanselmann
    return _HttpClient
313 02cab3e7 Michael Hanselmann
314 33231500 Michael Hanselmann
  def _Get(self, identity):
315 33231500 Michael Hanselmann
    """Gets an HTTP client from the pool.
316 02cab3e7 Michael Hanselmann

317 33231500 Michael Hanselmann
    @type identity: string
318 33231500 Michael Hanselmann
    @param identity: Client identifier
319 02cab3e7 Michael Hanselmann

320 33231500 Michael Hanselmann
    """
321 33231500 Michael Hanselmann
    try:
322 33231500 Michael Hanselmann
      pclient  = self._pool.pop(identity)
323 33231500 Michael Hanselmann
    except KeyError:
324 33231500 Michael Hanselmann
      # Need to create new client
325 33231500 Michael Hanselmann
      client = self._GetHttpClientCreator()(self._curl_config_fn)
326 33231500 Michael Hanselmann
      pclient = _PooledHttpClient(identity, client)
327 85061b9a Michael Hanselmann
      self._logger.debug("Created new client %s", pclient)
328 33231500 Michael Hanselmann
    else:
329 85061b9a Michael Hanselmann
      self._logger.debug("Reusing client %s", pclient)
330 02cab3e7 Michael Hanselmann
331 33231500 Michael Hanselmann
    assert pclient.identity == identity
332 02cab3e7 Michael Hanselmann
333 33231500 Michael Hanselmann
    return pclient
334 02cab3e7 Michael Hanselmann
335 33231500 Michael Hanselmann
  def _StartRequest(self, req):
336 33231500 Michael Hanselmann
    """Starts a request.
337 9fa2e150 Michael Hanselmann

338 33231500 Michael Hanselmann
    @type req: L{HttpClientRequest}
339 33231500 Michael Hanselmann
    @param req: HTTP request
340 9fa2e150 Michael Hanselmann

341 33231500 Michael Hanselmann
    """
342 33231500 Michael Hanselmann
    pclient = self._Get(req.identity)
343 02cab3e7 Michael Hanselmann
344 33231500 Michael Hanselmann
    assert req.identity not in self._pool
345 02cab3e7 Michael Hanselmann
346 33231500 Michael Hanselmann
    pclient.client.StartRequest(req)
347 33231500 Michael Hanselmann
    pclient.lastused = self._generation
348 33231500 Michael Hanselmann
349 33231500 Michael Hanselmann
    return pclient
350 02cab3e7 Michael Hanselmann
351 33231500 Michael Hanselmann
  def _Return(self, pclients):
352 33231500 Michael Hanselmann
    """Returns HTTP clients to the pool.
353 02cab3e7 Michael Hanselmann

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

377 33231500 Michael Hanselmann
    """
378 33231500 Michael Hanselmann
    return pycurl.CurlMulti()
379 02cab3e7 Michael Hanselmann
380 33231500 Michael Hanselmann
  def ProcessRequests(self, requests):
381 33231500 Michael Hanselmann
    """Processes any number of HTTP client requests using pooled objects.
382 02cab3e7 Michael Hanselmann

383 33231500 Michael Hanselmann
    @type requests: list of L{HttpClientRequest}
384 33231500 Michael Hanselmann
    @param requests: List of all requests
385 02cab3e7 Michael Hanselmann

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