PageRenderTime 49ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/other/FetchData/mechanize/_http.py

http://github.com/jbeezley/wrf-fire
Python | 758 lines | 614 code | 59 blank | 85 comment | 59 complexity | d0cafcc30d83e5551847089463114309 MD5 | raw file
Possible License(s): AGPL-1.0
  1. """HTTP related handlers.
  2. Note that some other HTTP handlers live in more specific modules: _auth.py,
  3. _gzip.py, etc.
  4. Copyright 2002-2006 John J Lee <jjl@pobox.com>
  5. This code is free software; you can redistribute it and/or modify it
  6. under the terms of the BSD or ZPL 2.1 licenses (see the file
  7. COPYING.txt included with the distribution).
  8. """
  9. import time, htmlentitydefs, logging, socket, \
  10. urllib2, urllib, httplib, sgmllib
  11. from urllib2 import URLError, HTTPError, BaseHandler
  12. from cStringIO import StringIO
  13. from _clientcookie import CookieJar
  14. from _headersutil import is_html
  15. from _html import unescape, unescape_charref
  16. from _request import Request
  17. from _response import closeable_response, response_seek_wrapper
  18. import _rfc3986
  19. import _sockettimeout
  20. debug = logging.getLogger("mechanize").debug
  21. debug_robots = logging.getLogger("mechanize.robots").debug
  22. # monkeypatch urllib2.HTTPError to show URL
  23. ## def urllib2_str(self):
  24. ## return 'HTTP Error %s: %s (%s)' % (
  25. ## self.code, self.msg, self.geturl())
  26. ## urllib2.HTTPError.__str__ = urllib2_str
  27. CHUNK = 1024 # size of chunks fed to HTML HEAD parser, in bytes
  28. DEFAULT_ENCODING = 'latin-1'
  29. try:
  30. socket._fileobject("fake socket", close=True)
  31. except TypeError:
  32. # python <= 2.4
  33. create_readline_wrapper = socket._fileobject
  34. else:
  35. def create_readline_wrapper(fh):
  36. return socket._fileobject(fh, close=True)
  37. # This adds "refresh" to the list of redirectables and provides a redirection
  38. # algorithm that doesn't go into a loop in the presence of cookies
  39. # (Python 2.4 has this new algorithm, 2.3 doesn't).
  40. class HTTPRedirectHandler(BaseHandler):
  41. # maximum number of redirections to any single URL
  42. # this is needed because of the state that cookies introduce
  43. max_repeats = 4
  44. # maximum total number of redirections (regardless of URL) before
  45. # assuming we're in a loop
  46. max_redirections = 10
  47. # Implementation notes:
  48. # To avoid the server sending us into an infinite loop, the request
  49. # object needs to track what URLs we have already seen. Do this by
  50. # adding a handler-specific attribute to the Request object. The value
  51. # of the dict is used to count the number of times the same URL has
  52. # been visited. This is needed because visiting the same URL twice
  53. # does not necessarily imply a loop, thanks to state introduced by
  54. # cookies.
  55. # Always unhandled redirection codes:
  56. # 300 Multiple Choices: should not handle this here.
  57. # 304 Not Modified: no need to handle here: only of interest to caches
  58. # that do conditional GETs
  59. # 305 Use Proxy: probably not worth dealing with here
  60. # 306 Unused: what was this for in the previous versions of protocol??
  61. def redirect_request(self, newurl, req, fp, code, msg, headers):
  62. """Return a Request or None in response to a redirect.
  63. This is called by the http_error_30x methods when a redirection
  64. response is received. If a redirection should take place, return a
  65. new Request to allow http_error_30x to perform the redirect;
  66. otherwise, return None to indicate that an HTTPError should be
  67. raised.
  68. """
  69. if code in (301, 302, 303, "refresh") or \
  70. (code == 307 and not req.has_data()):
  71. # Strictly (according to RFC 2616), 301 or 302 in response to
  72. # a POST MUST NOT cause a redirection without confirmation
  73. # from the user (of urllib2, in this case). In practice,
  74. # essentially all clients do redirect in this case, so we do
  75. # the same.
  76. # XXX really refresh redirections should be visiting; tricky to
  77. # fix, so this will wait until post-stable release
  78. new = Request(newurl,
  79. headers=req.headers,
  80. origin_req_host=req.get_origin_req_host(),
  81. unverifiable=True,
  82. visit=False,
  83. )
  84. new._origin_req = getattr(req, "_origin_req", req)
  85. return new
  86. else:
  87. raise HTTPError(req.get_full_url(), code, msg, headers, fp)
  88. def http_error_302(self, req, fp, code, msg, headers):
  89. # Some servers (incorrectly) return multiple Location headers
  90. # (so probably same goes for URI). Use first header.
  91. if headers.has_key('location'):
  92. newurl = headers.getheaders('location')[0]
  93. elif headers.has_key('uri'):
  94. newurl = headers.getheaders('uri')[0]
  95. else:
  96. return
  97. newurl = _rfc3986.clean_url(newurl, "latin-1")
  98. newurl = _rfc3986.urljoin(req.get_full_url(), newurl)
  99. # XXX Probably want to forget about the state of the current
  100. # request, although that might interact poorly with other
  101. # handlers that also use handler-specific request attributes
  102. new = self.redirect_request(newurl, req, fp, code, msg, headers)
  103. if new is None:
  104. return
  105. # loop detection
  106. # .redirect_dict has a key url if url was previously visited.
  107. if hasattr(req, 'redirect_dict'):
  108. visited = new.redirect_dict = req.redirect_dict
  109. if (visited.get(newurl, 0) >= self.max_repeats or
  110. len(visited) >= self.max_redirections):
  111. raise HTTPError(req.get_full_url(), code,
  112. self.inf_msg + msg, headers, fp)
  113. else:
  114. visited = new.redirect_dict = req.redirect_dict = {}
  115. visited[newurl] = visited.get(newurl, 0) + 1
  116. # Don't close the fp until we are sure that we won't use it
  117. # with HTTPError.
  118. fp.read()
  119. fp.close()
  120. return self.parent.open(new)
  121. http_error_301 = http_error_303 = http_error_307 = http_error_302
  122. http_error_refresh = http_error_302
  123. inf_msg = "The HTTP server returned a redirect error that would " \
  124. "lead to an infinite loop.\n" \
  125. "The last 30x error message was:\n"
  126. # XXX would self.reset() work, instead of raising this exception?
  127. class EndOfHeadError(Exception): pass
  128. class AbstractHeadParser:
  129. # only these elements are allowed in or before HEAD of document
  130. head_elems = ("html", "head",
  131. "title", "base",
  132. "script", "style", "meta", "link", "object")
  133. _entitydefs = htmlentitydefs.name2codepoint
  134. _encoding = DEFAULT_ENCODING
  135. def __init__(self):
  136. self.http_equiv = []
  137. def start_meta(self, attrs):
  138. http_equiv = content = None
  139. for key, value in attrs:
  140. if key == "http-equiv":
  141. http_equiv = self.unescape_attr_if_required(value)
  142. elif key == "content":
  143. content = self.unescape_attr_if_required(value)
  144. if http_equiv is not None and content is not None:
  145. self.http_equiv.append((http_equiv, content))
  146. def end_head(self):
  147. raise EndOfHeadError()
  148. def handle_entityref(self, name):
  149. #debug("%s", name)
  150. self.handle_data(unescape(
  151. '&%s;' % name, self._entitydefs, self._encoding))
  152. def handle_charref(self, name):
  153. #debug("%s", name)
  154. self.handle_data(unescape_charref(name, self._encoding))
  155. def unescape_attr(self, name):
  156. #debug("%s", name)
  157. return unescape(name, self._entitydefs, self._encoding)
  158. def unescape_attrs(self, attrs):
  159. #debug("%s", attrs)
  160. escaped_attrs = {}
  161. for key, val in attrs.items():
  162. escaped_attrs[key] = self.unescape_attr(val)
  163. return escaped_attrs
  164. def unknown_entityref(self, ref):
  165. self.handle_data("&%s;" % ref)
  166. def unknown_charref(self, ref):
  167. self.handle_data("&#%s;" % ref)
  168. try:
  169. import HTMLParser
  170. except ImportError:
  171. pass
  172. else:
  173. class XHTMLCompatibleHeadParser(AbstractHeadParser,
  174. HTMLParser.HTMLParser):
  175. def __init__(self):
  176. HTMLParser.HTMLParser.__init__(self)
  177. AbstractHeadParser.__init__(self)
  178. def handle_starttag(self, tag, attrs):
  179. if tag not in self.head_elems:
  180. raise EndOfHeadError()
  181. try:
  182. method = getattr(self, 'start_' + tag)
  183. except AttributeError:
  184. try:
  185. method = getattr(self, 'do_' + tag)
  186. except AttributeError:
  187. pass # unknown tag
  188. else:
  189. method(attrs)
  190. else:
  191. method(attrs)
  192. def handle_endtag(self, tag):
  193. if tag not in self.head_elems:
  194. raise EndOfHeadError()
  195. try:
  196. method = getattr(self, 'end_' + tag)
  197. except AttributeError:
  198. pass # unknown tag
  199. else:
  200. method()
  201. def unescape(self, name):
  202. # Use the entitydefs passed into constructor, not
  203. # HTMLParser.HTMLParser's entitydefs.
  204. return self.unescape_attr(name)
  205. def unescape_attr_if_required(self, name):
  206. return name # HTMLParser.HTMLParser already did it
  207. class HeadParser(AbstractHeadParser, sgmllib.SGMLParser):
  208. def _not_called(self):
  209. assert False
  210. def __init__(self):
  211. sgmllib.SGMLParser.__init__(self)
  212. AbstractHeadParser.__init__(self)
  213. def handle_starttag(self, tag, method, attrs):
  214. if tag not in self.head_elems:
  215. raise EndOfHeadError()
  216. if tag == "meta":
  217. method(attrs)
  218. def unknown_starttag(self, tag, attrs):
  219. self.handle_starttag(tag, self._not_called, attrs)
  220. def handle_endtag(self, tag, method):
  221. if tag in self.head_elems:
  222. method()
  223. else:
  224. raise EndOfHeadError()
  225. def unescape_attr_if_required(self, name):
  226. return self.unescape_attr(name)
  227. def parse_head(fileobj, parser):
  228. """Return a list of key, value pairs."""
  229. while 1:
  230. data = fileobj.read(CHUNK)
  231. try:
  232. parser.feed(data)
  233. except EndOfHeadError:
  234. break
  235. if len(data) != CHUNK:
  236. # this should only happen if there is no HTML body, or if
  237. # CHUNK is big
  238. break
  239. return parser.http_equiv
  240. class HTTPEquivProcessor(BaseHandler):
  241. """Append META HTTP-EQUIV headers to regular HTTP headers."""
  242. handler_order = 300 # before handlers that look at HTTP headers
  243. def __init__(self, head_parser_class=HeadParser,
  244. i_want_broken_xhtml_support=False,
  245. ):
  246. self.head_parser_class = head_parser_class
  247. self._allow_xhtml = i_want_broken_xhtml_support
  248. def http_response(self, request, response):
  249. if not hasattr(response, "seek"):
  250. response = response_seek_wrapper(response)
  251. http_message = response.info()
  252. url = response.geturl()
  253. ct_hdrs = http_message.getheaders("content-type")
  254. if is_html(ct_hdrs, url, self._allow_xhtml):
  255. try:
  256. try:
  257. html_headers = parse_head(response,
  258. self.head_parser_class())
  259. finally:
  260. response.seek(0)
  261. except (HTMLParser.HTMLParseError,
  262. sgmllib.SGMLParseError):
  263. pass
  264. else:
  265. for hdr, val in html_headers:
  266. # add a header
  267. http_message.dict[hdr.lower()] = val
  268. text = hdr + ": " + val
  269. for line in text.split("\n"):
  270. http_message.headers.append(line + "\n")
  271. return response
  272. https_response = http_response
  273. class HTTPCookieProcessor(BaseHandler):
  274. """Handle HTTP cookies.
  275. Public attributes:
  276. cookiejar: CookieJar instance
  277. """
  278. def __init__(self, cookiejar=None):
  279. if cookiejar is None:
  280. cookiejar = CookieJar()
  281. self.cookiejar = cookiejar
  282. def http_request(self, request):
  283. self.cookiejar.add_cookie_header(request)
  284. return request
  285. def http_response(self, request, response):
  286. self.cookiejar.extract_cookies(response, request)
  287. return response
  288. https_request = http_request
  289. https_response = http_response
  290. try:
  291. import robotparser
  292. except ImportError:
  293. pass
  294. else:
  295. class MechanizeRobotFileParser(robotparser.RobotFileParser):
  296. def __init__(self, url='', opener=None):
  297. robotparser.RobotFileParser.__init__(self, url)
  298. self._opener = opener
  299. self._timeout = _sockettimeout._GLOBAL_DEFAULT_TIMEOUT
  300. def set_opener(self, opener=None):
  301. import _opener
  302. if opener is None:
  303. opener = _opener.OpenerDirector()
  304. self._opener = opener
  305. def set_timeout(self, timeout):
  306. self._timeout = timeout
  307. def read(self):
  308. """Reads the robots.txt URL and feeds it to the parser."""
  309. if self._opener is None:
  310. self.set_opener()
  311. req = Request(self.url, unverifiable=True, visit=False,
  312. timeout=self._timeout)
  313. try:
  314. f = self._opener.open(req)
  315. except HTTPError, f:
  316. pass
  317. except (IOError, socket.error, OSError), exc:
  318. debug_robots("ignoring error opening %r: %s" %
  319. (self.url, exc))
  320. return
  321. lines = []
  322. line = f.readline()
  323. while line:
  324. lines.append(line.strip())
  325. line = f.readline()
  326. status = f.code
  327. if status == 401 or status == 403:
  328. self.disallow_all = True
  329. debug_robots("disallow all")
  330. elif status >= 400:
  331. self.allow_all = True
  332. debug_robots("allow all")
  333. elif status == 200 and lines:
  334. debug_robots("parse lines")
  335. self.parse(lines)
  336. class RobotExclusionError(urllib2.HTTPError):
  337. def __init__(self, request, *args):
  338. apply(urllib2.HTTPError.__init__, (self,)+args)
  339. self.request = request
  340. class HTTPRobotRulesProcessor(BaseHandler):
  341. # before redirections, after everything else
  342. handler_order = 800
  343. try:
  344. from httplib import HTTPMessage
  345. except:
  346. from mimetools import Message
  347. http_response_class = Message
  348. else:
  349. http_response_class = HTTPMessage
  350. def __init__(self, rfp_class=MechanizeRobotFileParser):
  351. self.rfp_class = rfp_class
  352. self.rfp = None
  353. self._host = None
  354. def http_request(self, request):
  355. scheme = request.get_type()
  356. if scheme not in ["http", "https"]:
  357. # robots exclusion only applies to HTTP
  358. return request
  359. if request.get_selector() == "/robots.txt":
  360. # /robots.txt is always OK to fetch
  361. return request
  362. host = request.get_host()
  363. # robots.txt requests don't need to be allowed by robots.txt :-)
  364. origin_req = getattr(request, "_origin_req", None)
  365. if (origin_req is not None and
  366. origin_req.get_selector() == "/robots.txt" and
  367. origin_req.get_host() == host
  368. ):
  369. return request
  370. if host != self._host:
  371. self.rfp = self.rfp_class()
  372. try:
  373. self.rfp.set_opener(self.parent)
  374. except AttributeError:
  375. debug("%r instance does not support set_opener" %
  376. self.rfp.__class__)
  377. self.rfp.set_url(scheme+"://"+host+"/robots.txt")
  378. self.rfp.set_timeout(request.timeout)
  379. self.rfp.read()
  380. self._host = host
  381. ua = request.get_header("User-agent", "")
  382. if self.rfp.can_fetch(ua, request.get_full_url()):
  383. return request
  384. else:
  385. # XXX This should really have raised URLError. Too late now...
  386. msg = "request disallowed by robots.txt"
  387. raise RobotExclusionError(
  388. request,
  389. request.get_full_url(),
  390. 403, msg,
  391. self.http_response_class(StringIO()), StringIO(msg))
  392. https_request = http_request
  393. class HTTPRefererProcessor(BaseHandler):
  394. """Add Referer header to requests.
  395. This only makes sense if you use each RefererProcessor for a single
  396. chain of requests only (so, for example, if you use a single
  397. HTTPRefererProcessor to fetch a series of URLs extracted from a single
  398. page, this will break).
  399. There's a proper implementation of this in mechanize.Browser.
  400. """
  401. def __init__(self):
  402. self.referer = None
  403. def http_request(self, request):
  404. if ((self.referer is not None) and
  405. not request.has_header("Referer")):
  406. request.add_unredirected_header("Referer", self.referer)
  407. return request
  408. def http_response(self, request, response):
  409. self.referer = response.geturl()
  410. return response
  411. https_request = http_request
  412. https_response = http_response
  413. def clean_refresh_url(url):
  414. # e.g. Firefox 1.5 does (something like) this
  415. if ((url.startswith('"') and url.endswith('"')) or
  416. (url.startswith("'") and url.endswith("'"))):
  417. url = url[1:-1]
  418. return _rfc3986.clean_url(url, "latin-1") # XXX encoding
  419. def parse_refresh_header(refresh):
  420. """
  421. >>> parse_refresh_header("1; url=http://example.com/")
  422. (1.0, 'http://example.com/')
  423. >>> parse_refresh_header("1; url='http://example.com/'")
  424. (1.0, 'http://example.com/')
  425. >>> parse_refresh_header("1")
  426. (1.0, None)
  427. >>> parse_refresh_header("blah")
  428. Traceback (most recent call last):
  429. ValueError: invalid literal for float(): blah
  430. """
  431. ii = refresh.find(";")
  432. if ii != -1:
  433. pause, newurl_spec = float(refresh[:ii]), refresh[ii+1:]
  434. jj = newurl_spec.find("=")
  435. key = None
  436. if jj != -1:
  437. key, newurl = newurl_spec[:jj], newurl_spec[jj+1:]
  438. newurl = clean_refresh_url(newurl)
  439. if key is None or key.strip().lower() != "url":
  440. raise ValueError()
  441. else:
  442. pause, newurl = float(refresh), None
  443. return pause, newurl
  444. class HTTPRefreshProcessor(BaseHandler):
  445. """Perform HTTP Refresh redirections.
  446. Note that if a non-200 HTTP code has occurred (for example, a 30x
  447. redirect), this processor will do nothing.
  448. By default, only zero-time Refresh headers are redirected. Use the
  449. max_time attribute / constructor argument to allow Refresh with longer
  450. pauses. Use the honor_time attribute / constructor argument to control
  451. whether the requested pause is honoured (with a time.sleep()) or
  452. skipped in favour of immediate redirection.
  453. Public attributes:
  454. max_time: see above
  455. honor_time: see above
  456. """
  457. handler_order = 1000
  458. def __init__(self, max_time=0, honor_time=True):
  459. self.max_time = max_time
  460. self.honor_time = honor_time
  461. self._sleep = time.sleep
  462. def http_response(self, request, response):
  463. code, msg, hdrs = response.code, response.msg, response.info()
  464. if code == 200 and hdrs.has_key("refresh"):
  465. refresh = hdrs.getheaders("refresh")[0]
  466. try:
  467. pause, newurl = parse_refresh_header(refresh)
  468. except ValueError:
  469. debug("bad Refresh header: %r" % refresh)
  470. return response
  471. if newurl is None:
  472. newurl = response.geturl()
  473. if (self.max_time is None) or (pause <= self.max_time):
  474. if pause > 1E-3 and self.honor_time:
  475. self._sleep(pause)
  476. hdrs["location"] = newurl
  477. # hardcoded http is NOT a bug
  478. response = self.parent.error(
  479. "http", request, response,
  480. "refresh", msg, hdrs)
  481. else:
  482. debug("Refresh header ignored: %r" % refresh)
  483. return response
  484. https_response = http_response
  485. class HTTPErrorProcessor(BaseHandler):
  486. """Process HTTP error responses.
  487. The purpose of this handler is to to allow other response processors a
  488. look-in by removing the call to parent.error() from
  489. AbstractHTTPHandler.
  490. For non-200 error codes, this just passes the job on to the
  491. Handler.<proto>_error_<code> methods, via the OpenerDirector.error
  492. method. Eventually, urllib2.HTTPDefaultErrorHandler will raise an
  493. HTTPError if no other handler handles the error.
  494. """
  495. handler_order = 1000 # after all other processors
  496. def http_response(self, request, response):
  497. code, msg, hdrs = response.code, response.msg, response.info()
  498. if code != 200:
  499. # hardcoded http is NOT a bug
  500. response = self.parent.error(
  501. "http", request, response, code, msg, hdrs)
  502. return response
  503. https_response = http_response
  504. class HTTPDefaultErrorHandler(BaseHandler):
  505. def http_error_default(self, req, fp, code, msg, hdrs):
  506. # why these error methods took the code, msg, headers args in the first
  507. # place rather than a response object, I don't know, but to avoid
  508. # multiple wrapping, we're discarding them
  509. if isinstance(fp, urllib2.HTTPError):
  510. response = fp
  511. else:
  512. response = urllib2.HTTPError(
  513. req.get_full_url(), code, msg, hdrs, fp)
  514. assert code == response.code
  515. assert msg == response.msg
  516. assert hdrs == response.hdrs
  517. raise response
  518. class AbstractHTTPHandler(BaseHandler):
  519. def __init__(self, debuglevel=0):
  520. self._debuglevel = debuglevel
  521. def set_http_debuglevel(self, level):
  522. self._debuglevel = level
  523. def do_request_(self, request):
  524. host = request.get_host()
  525. if not host:
  526. raise URLError('no host given')
  527. if request.has_data(): # POST
  528. data = request.get_data()
  529. if not request.has_header('Content-type'):
  530. request.add_unredirected_header(
  531. 'Content-type',
  532. 'application/x-www-form-urlencoded')
  533. if not request.has_header('Content-length'):
  534. request.add_unredirected_header(
  535. 'Content-length', '%d' % len(data))
  536. scheme, sel = urllib.splittype(request.get_selector())
  537. sel_host, sel_path = urllib.splithost(sel)
  538. if not request.has_header('Host'):
  539. request.add_unredirected_header('Host', sel_host or host)
  540. for name, value in self.parent.addheaders:
  541. name = name.capitalize()
  542. if not request.has_header(name):
  543. request.add_unredirected_header(name, value)
  544. return request
  545. def do_open(self, http_class, req):
  546. """Return an addinfourl object for the request, using http_class.
  547. http_class must implement the HTTPConnection API from httplib.
  548. The addinfourl return value is a file-like object. It also
  549. has methods and attributes including:
  550. - info(): return a mimetools.Message object for the headers
  551. - geturl(): return the original request URL
  552. - code: HTTP status code
  553. """
  554. host_port = req.get_host()
  555. if not host_port:
  556. raise URLError('no host given')
  557. try:
  558. h = http_class(host_port, timeout=req.timeout)
  559. except TypeError:
  560. # Python < 2.6, no per-connection timeout support
  561. h = http_class(host_port)
  562. h.set_debuglevel(self._debuglevel)
  563. headers = dict(req.headers)
  564. headers.update(req.unredirected_hdrs)
  565. # We want to make an HTTP/1.1 request, but the addinfourl
  566. # class isn't prepared to deal with a persistent connection.
  567. # It will try to read all remaining data from the socket,
  568. # which will block while the server waits for the next request.
  569. # So make sure the connection gets closed after the (only)
  570. # request.
  571. headers["Connection"] = "close"
  572. headers = dict(
  573. [(name.title(), val) for name, val in headers.items()])
  574. try:
  575. h.request(req.get_method(), req.get_selector(), req.data, headers)
  576. r = h.getresponse()
  577. except socket.error, err: # XXX what error?
  578. raise URLError(err)
  579. # Pick apart the HTTPResponse object to get the addinfourl
  580. # object initialized properly.
  581. # Wrap the HTTPResponse object in socket's file object adapter
  582. # for Windows. That adapter calls recv(), so delegate recv()
  583. # to read(). This weird wrapping allows the returned object to
  584. # have readline() and readlines() methods.
  585. # XXX It might be better to extract the read buffering code
  586. # out of socket._fileobject() and into a base class.
  587. r.recv = r.read
  588. fp = create_readline_wrapper(r)
  589. resp = closeable_response(fp, r.msg, req.get_full_url(),
  590. r.status, r.reason)
  591. return resp
  592. class HTTPHandler(AbstractHTTPHandler):
  593. def http_open(self, req):
  594. return self.do_open(httplib.HTTPConnection, req)
  595. http_request = AbstractHTTPHandler.do_request_
  596. if hasattr(httplib, 'HTTPS'):
  597. class HTTPSConnectionFactory:
  598. def __init__(self, key_file, cert_file):
  599. self._key_file = key_file
  600. self._cert_file = cert_file
  601. def __call__(self, hostport):
  602. return httplib.HTTPSConnection(
  603. hostport,
  604. key_file=self._key_file, cert_file=self._cert_file)
  605. class HTTPSHandler(AbstractHTTPHandler):
  606. def __init__(self, client_cert_manager=None):
  607. AbstractHTTPHandler.__init__(self)
  608. self.client_cert_manager = client_cert_manager
  609. def https_open(self, req):
  610. if self.client_cert_manager is not None:
  611. key_file, cert_file = self.client_cert_manager.find_key_cert(
  612. req.get_full_url())
  613. conn_factory = HTTPSConnectionFactory(key_file, cert_file)
  614. else:
  615. conn_factory = httplib.HTTPSConnection
  616. return self.do_open(conn_factory, req)
  617. https_request = AbstractHTTPHandler.do_request_