remote_connection.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. # Licensed to the Software Freedom Conservancy (SFC) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The SFC licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. import logging
  18. import socket
  19. import string
  20. import base64
  21. try:
  22. import http.client as httplib
  23. from urllib import request as url_request
  24. from urllib import parse
  25. except ImportError: # above is available in py3+, below is py2.7
  26. import httplib as httplib
  27. import urllib2 as url_request
  28. import urlparse as parse
  29. from selenium.webdriver.common import utils as common_utils
  30. from .command import Command
  31. from .errorhandler import ErrorCode
  32. from . import utils
  33. LOGGER = logging.getLogger(__name__)
  34. class Request(url_request.Request):
  35. """
  36. Extends the url_request.Request to support all HTTP request types.
  37. """
  38. def __init__(self, url, data=None, method=None):
  39. """
  40. Initialise a new HTTP request.
  41. :Args:
  42. - url - String for the URL to send the request to.
  43. - data - Data to send with the request.
  44. """
  45. if method is None:
  46. method = data is not None and 'POST' or 'GET'
  47. elif method != 'POST' and method != 'PUT':
  48. data = None
  49. self._method = method
  50. url_request.Request.__init__(self, url, data=data)
  51. def get_method(self):
  52. """
  53. Returns the HTTP method used by this request.
  54. """
  55. return self._method
  56. class Response(object):
  57. """
  58. Represents an HTTP response.
  59. """
  60. def __init__(self, fp, code, headers, url):
  61. """
  62. Initialise a new Response.
  63. :Args:
  64. - fp - The response body file object.
  65. - code - The HTTP status code returned by the server.
  66. - headers - A dictionary of headers returned by the server.
  67. - url - URL of the retrieved resource represented by this Response.
  68. """
  69. self.fp = fp
  70. self.read = fp.read
  71. self.code = code
  72. self.headers = headers
  73. self.url = url
  74. def close(self):
  75. """
  76. Close the response body file object.
  77. """
  78. self.read = None
  79. self.fp = None
  80. def info(self):
  81. """
  82. Returns the response headers.
  83. """
  84. return self.headers
  85. def geturl(self):
  86. """
  87. Returns the URL for the resource returned in this response.
  88. """
  89. return self.url
  90. class HttpErrorHandler(url_request.HTTPDefaultErrorHandler):
  91. """
  92. A custom HTTP error handler.
  93. Used to return Response objects instead of raising an HTTPError exception.
  94. """
  95. def http_error_default(self, req, fp, code, msg, headers):
  96. """
  97. Default HTTP error handler.
  98. :Args:
  99. - req - The original Request object.
  100. - fp - The response body file object.
  101. - code - The HTTP status code returned by the server.
  102. - msg - The HTTP status message returned by the server.
  103. - headers - The response headers.
  104. :Returns:
  105. A new Response object.
  106. """
  107. return Response(fp, code, headers, req.get_full_url())
  108. class RemoteConnection(object):
  109. """A connection with the Remote WebDriver server.
  110. Communicates with the server using the WebDriver wire protocol:
  111. https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol"""
  112. _timeout = socket._GLOBAL_DEFAULT_TIMEOUT
  113. @classmethod
  114. def get_timeout(cls):
  115. """
  116. :Returns:
  117. Timeout value in seconds for all http requests made to the Remote Connection
  118. """
  119. return None if cls._timeout == socket._GLOBAL_DEFAULT_TIMEOUT else cls._timeout
  120. @classmethod
  121. def set_timeout(cls, timeout):
  122. """
  123. Override the default timeout
  124. :Args:
  125. - timeout - timeout value for http requests in seconds
  126. """
  127. cls._timeout = timeout
  128. @classmethod
  129. def reset_timeout(cls):
  130. """
  131. Reset the http request timeout to socket._GLOBAL_DEFAULT_TIMEOUT
  132. """
  133. cls._timeout = socket._GLOBAL_DEFAULT_TIMEOUT
  134. @classmethod
  135. def get_remote_connection_headers(cls, parsed_url, keep_alive=False):
  136. """
  137. Get headers for remote request.
  138. :Args:
  139. - parsed_url - The parsed url
  140. - keep_alive (Boolean) - Is this a keep-alive connection (default: False)
  141. """
  142. headers = {
  143. 'Accept': 'application/json',
  144. 'Content-Type': 'application/json;charset=UTF-8',
  145. 'User-Agent': 'Python http auth'
  146. }
  147. if parsed_url.username:
  148. base64string = base64.b64encode('{0.username}:{0.password}'.format(parsed_url).encode())
  149. headers.update({
  150. 'Authorization': 'Basic {}'.format(base64string.decode())
  151. })
  152. if keep_alive:
  153. headers.update({
  154. 'Connection': 'keep-alive'
  155. })
  156. return headers
  157. def __init__(self, remote_server_addr, keep_alive=False, resolve_ip=True):
  158. # Attempt to resolve the hostname and get an IP address.
  159. self.keep_alive = keep_alive
  160. parsed_url = parse.urlparse(remote_server_addr)
  161. addr = parsed_url.hostname
  162. if parsed_url.hostname and resolve_ip:
  163. port = parsed_url.port or None
  164. if parsed_url.scheme == "https":
  165. ip = parsed_url.hostname
  166. else:
  167. ip = common_utils.find_connectable_ip(parsed_url.hostname,
  168. port=port)
  169. if ip:
  170. netloc = ip
  171. addr = netloc
  172. if parsed_url.port:
  173. netloc = common_utils.join_host_port(netloc,
  174. parsed_url.port)
  175. if parsed_url.username:
  176. auth = parsed_url.username
  177. if parsed_url.password:
  178. auth += ':%s' % parsed_url.password
  179. netloc = '%s@%s' % (auth, netloc)
  180. remote_server_addr = parse.urlunparse(
  181. (parsed_url.scheme, netloc, parsed_url.path,
  182. parsed_url.params, parsed_url.query, parsed_url.fragment))
  183. else:
  184. LOGGER.info('Could not get IP address for host: %s' %
  185. parsed_url.hostname)
  186. self._url = remote_server_addr
  187. if keep_alive:
  188. self._conn = httplib.HTTPConnection(
  189. str(addr), str(parsed_url.port), timeout=self._timeout)
  190. self._commands = {
  191. Command.STATUS: ('GET', '/status'),
  192. Command.NEW_SESSION: ('POST', '/session'),
  193. Command.GET_ALL_SESSIONS: ('GET', '/sessions'),
  194. Command.QUIT: ('DELETE', '/session/$sessionId'),
  195. Command.GET_CURRENT_WINDOW_HANDLE:
  196. ('GET', '/session/$sessionId/window_handle'),
  197. Command.W3C_GET_CURRENT_WINDOW_HANDLE:
  198. ('GET', '/session/$sessionId/window'),
  199. Command.GET_WINDOW_HANDLES:
  200. ('GET', '/session/$sessionId/window_handles'),
  201. Command.W3C_GET_WINDOW_HANDLES:
  202. ('GET', '/session/$sessionId/window/handles'),
  203. Command.GET: ('POST', '/session/$sessionId/url'),
  204. Command.GO_FORWARD: ('POST', '/session/$sessionId/forward'),
  205. Command.GO_BACK: ('POST', '/session/$sessionId/back'),
  206. Command.REFRESH: ('POST', '/session/$sessionId/refresh'),
  207. Command.EXECUTE_SCRIPT: ('POST', '/session/$sessionId/execute'),
  208. Command.W3C_EXECUTE_SCRIPT:
  209. ('POST', '/session/$sessionId/execute/sync'),
  210. Command.W3C_EXECUTE_SCRIPT_ASYNC:
  211. ('POST', '/session/$sessionId/execute/async'),
  212. Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'),
  213. Command.GET_TITLE: ('GET', '/session/$sessionId/title'),
  214. Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'),
  215. Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'),
  216. Command.ELEMENT_SCREENSHOT: ('GET', '/session/$sessionId/element/$id/screenshot'),
  217. Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'),
  218. Command.FIND_ELEMENTS: ('POST', '/session/$sessionId/elements'),
  219. Command.W3C_GET_ACTIVE_ELEMENT: ('GET', '/session/$sessionId/element/active'),
  220. Command.GET_ACTIVE_ELEMENT:
  221. ('POST', '/session/$sessionId/element/active'),
  222. Command.FIND_CHILD_ELEMENT:
  223. ('POST', '/session/$sessionId/element/$id/element'),
  224. Command.FIND_CHILD_ELEMENTS:
  225. ('POST', '/session/$sessionId/element/$id/elements'),
  226. Command.CLICK_ELEMENT: ('POST', '/session/$sessionId/element/$id/click'),
  227. Command.CLEAR_ELEMENT: ('POST', '/session/$sessionId/element/$id/clear'),
  228. Command.SUBMIT_ELEMENT: ('POST', '/session/$sessionId/element/$id/submit'),
  229. Command.GET_ELEMENT_TEXT: ('GET', '/session/$sessionId/element/$id/text'),
  230. Command.SEND_KEYS_TO_ELEMENT:
  231. ('POST', '/session/$sessionId/element/$id/value'),
  232. Command.SEND_KEYS_TO_ACTIVE_ELEMENT:
  233. ('POST', '/session/$sessionId/keys'),
  234. Command.UPLOAD_FILE: ('POST', "/session/$sessionId/file"),
  235. Command.GET_ELEMENT_VALUE:
  236. ('GET', '/session/$sessionId/element/$id/value'),
  237. Command.GET_ELEMENT_TAG_NAME:
  238. ('GET', '/session/$sessionId/element/$id/name'),
  239. Command.IS_ELEMENT_SELECTED:
  240. ('GET', '/session/$sessionId/element/$id/selected'),
  241. Command.SET_ELEMENT_SELECTED:
  242. ('POST', '/session/$sessionId/element/$id/selected'),
  243. Command.IS_ELEMENT_ENABLED:
  244. ('GET', '/session/$sessionId/element/$id/enabled'),
  245. Command.IS_ELEMENT_DISPLAYED:
  246. ('GET', '/session/$sessionId/element/$id/displayed'),
  247. Command.GET_ELEMENT_LOCATION:
  248. ('GET', '/session/$sessionId/element/$id/location'),
  249. Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW:
  250. ('GET', '/session/$sessionId/element/$id/location_in_view'),
  251. Command.GET_ELEMENT_SIZE:
  252. ('GET', '/session/$sessionId/element/$id/size'),
  253. Command.GET_ELEMENT_RECT:
  254. ('GET', '/session/$sessionId/element/$id/rect'),
  255. Command.GET_ELEMENT_ATTRIBUTE:
  256. ('GET', '/session/$sessionId/element/$id/attribute/$name'),
  257. Command.GET_ELEMENT_PROPERTY:
  258. ('GET', '/session/$sessionId/element/$id/property/$name'),
  259. Command.ELEMENT_EQUALS:
  260. ('GET', '/session/$sessionId/element/$id/equals/$other'),
  261. Command.GET_ALL_COOKIES: ('GET', '/session/$sessionId/cookie'),
  262. Command.ADD_COOKIE: ('POST', '/session/$sessionId/cookie'),
  263. Command.DELETE_ALL_COOKIES:
  264. ('DELETE', '/session/$sessionId/cookie'),
  265. Command.DELETE_COOKIE:
  266. ('DELETE', '/session/$sessionId/cookie/$name'),
  267. Command.SWITCH_TO_FRAME: ('POST', '/session/$sessionId/frame'),
  268. Command.SWITCH_TO_PARENT_FRAME: ('POST', '/session/$sessionId/frame/parent'),
  269. Command.SWITCH_TO_WINDOW: ('POST', '/session/$sessionId/window'),
  270. Command.CLOSE: ('DELETE', '/session/$sessionId/window'),
  271. Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY:
  272. ('GET', '/session/$sessionId/element/$id/css/$propertyName'),
  273. Command.IMPLICIT_WAIT:
  274. ('POST', '/session/$sessionId/timeouts/implicit_wait'),
  275. Command.EXECUTE_ASYNC_SCRIPT: ('POST', '/session/$sessionId/execute_async'),
  276. Command.SET_SCRIPT_TIMEOUT:
  277. ('POST', '/session/$sessionId/timeouts/async_script'),
  278. Command.SET_TIMEOUTS:
  279. ('POST', '/session/$sessionId/timeouts'),
  280. Command.DISMISS_ALERT:
  281. ('POST', '/session/$sessionId/dismiss_alert'),
  282. Command.W3C_DISMISS_ALERT:
  283. ('POST', '/session/$sessionId/alert/dismiss'),
  284. Command.ACCEPT_ALERT:
  285. ('POST', '/session/$sessionId/accept_alert'),
  286. Command.W3C_ACCEPT_ALERT:
  287. ('POST', '/session/$sessionId/alert/accept'),
  288. Command.SET_ALERT_VALUE:
  289. ('POST', '/session/$sessionId/alert_text'),
  290. Command.W3C_SET_ALERT_VALUE:
  291. ('POST', '/session/$sessionId/alert/text'),
  292. Command.GET_ALERT_TEXT:
  293. ('GET', '/session/$sessionId/alert_text'),
  294. Command.W3C_GET_ALERT_TEXT:
  295. ('GET', '/session/$sessionId/alert/text'),
  296. Command.SET_ALERT_CREDENTIALS:
  297. ('POST', '/session/$sessionId/alert/credentials'),
  298. Command.CLICK:
  299. ('POST', '/session/$sessionId/click'),
  300. Command.W3C_ACTIONS:
  301. ('POST', '/session/$sessionId/actions'),
  302. Command.W3C_CLEAR_ACTIONS:
  303. ('DELETE', '/session/$sessionId/actions'),
  304. Command.DOUBLE_CLICK:
  305. ('POST', '/session/$sessionId/doubleclick'),
  306. Command.MOUSE_DOWN:
  307. ('POST', '/session/$sessionId/buttondown'),
  308. Command.MOUSE_UP:
  309. ('POST', '/session/$sessionId/buttonup'),
  310. Command.MOVE_TO:
  311. ('POST', '/session/$sessionId/moveto'),
  312. Command.GET_WINDOW_SIZE:
  313. ('GET', '/session/$sessionId/window/$windowHandle/size'),
  314. Command.W3C_GET_WINDOW_SIZE:
  315. ('GET', '/session/$sessionId/window/size'),
  316. Command.SET_WINDOW_SIZE:
  317. ('POST', '/session/$sessionId/window/$windowHandle/size'),
  318. Command.W3C_SET_WINDOW_SIZE:
  319. ('POST', '/session/$sessionId/window/size'),
  320. Command.GET_WINDOW_POSITION:
  321. ('GET', '/session/$sessionId/window/$windowHandle/position'),
  322. Command.SET_WINDOW_POSITION:
  323. ('POST', '/session/$sessionId/window/$windowHandle/position'),
  324. Command.W3C_GET_WINDOW_POSITION:
  325. ('GET', '/session/$sessionId/window/position'),
  326. Command.W3C_SET_WINDOW_POSITION:
  327. ('POST', '/session/$sessionId/window/position'),
  328. Command.SET_WINDOW_RECT:
  329. ('POST', '/session/$sessionId/window/rect'),
  330. Command.GET_WINDOW_RECT:
  331. ('GET', '/session/$sessionId/window/rect'),
  332. Command.MAXIMIZE_WINDOW:
  333. ('POST', '/session/$sessionId/window/$windowHandle/maximize'),
  334. Command.W3C_MAXIMIZE_WINDOW:
  335. ('POST', '/session/$sessionId/window/maximize'),
  336. Command.SET_SCREEN_ORIENTATION:
  337. ('POST', '/session/$sessionId/orientation'),
  338. Command.GET_SCREEN_ORIENTATION:
  339. ('GET', '/session/$sessionId/orientation'),
  340. Command.SINGLE_TAP:
  341. ('POST', '/session/$sessionId/touch/click'),
  342. Command.TOUCH_DOWN:
  343. ('POST', '/session/$sessionId/touch/down'),
  344. Command.TOUCH_UP:
  345. ('POST', '/session/$sessionId/touch/up'),
  346. Command.TOUCH_MOVE:
  347. ('POST', '/session/$sessionId/touch/move'),
  348. Command.TOUCH_SCROLL:
  349. ('POST', '/session/$sessionId/touch/scroll'),
  350. Command.DOUBLE_TAP:
  351. ('POST', '/session/$sessionId/touch/doubleclick'),
  352. Command.LONG_PRESS:
  353. ('POST', '/session/$sessionId/touch/longclick'),
  354. Command.FLICK:
  355. ('POST', '/session/$sessionId/touch/flick'),
  356. Command.EXECUTE_SQL:
  357. ('POST', '/session/$sessionId/execute_sql'),
  358. Command.GET_LOCATION:
  359. ('GET', '/session/$sessionId/location'),
  360. Command.SET_LOCATION:
  361. ('POST', '/session/$sessionId/location'),
  362. Command.GET_APP_CACHE:
  363. ('GET', '/session/$sessionId/application_cache'),
  364. Command.GET_APP_CACHE_STATUS:
  365. ('GET', '/session/$sessionId/application_cache/status'),
  366. Command.CLEAR_APP_CACHE:
  367. ('DELETE', '/session/$sessionId/application_cache/clear'),
  368. Command.GET_NETWORK_CONNECTION:
  369. ('GET', '/session/$sessionId/network_connection'),
  370. Command.SET_NETWORK_CONNECTION:
  371. ('POST', '/session/$sessionId/network_connection'),
  372. Command.GET_LOCAL_STORAGE_ITEM:
  373. ('GET', '/session/$sessionId/local_storage/key/$key'),
  374. Command.REMOVE_LOCAL_STORAGE_ITEM:
  375. ('DELETE', '/session/$sessionId/local_storage/key/$key'),
  376. Command.GET_LOCAL_STORAGE_KEYS:
  377. ('GET', '/session/$sessionId/local_storage'),
  378. Command.SET_LOCAL_STORAGE_ITEM:
  379. ('POST', '/session/$sessionId/local_storage'),
  380. Command.CLEAR_LOCAL_STORAGE:
  381. ('DELETE', '/session/$sessionId/local_storage'),
  382. Command.GET_LOCAL_STORAGE_SIZE:
  383. ('GET', '/session/$sessionId/local_storage/size'),
  384. Command.GET_SESSION_STORAGE_ITEM:
  385. ('GET', '/session/$sessionId/session_storage/key/$key'),
  386. Command.REMOVE_SESSION_STORAGE_ITEM:
  387. ('DELETE', '/session/$sessionId/session_storage/key/$key'),
  388. Command.GET_SESSION_STORAGE_KEYS:
  389. ('GET', '/session/$sessionId/session_storage'),
  390. Command.SET_SESSION_STORAGE_ITEM:
  391. ('POST', '/session/$sessionId/session_storage'),
  392. Command.CLEAR_SESSION_STORAGE:
  393. ('DELETE', '/session/$sessionId/session_storage'),
  394. Command.GET_SESSION_STORAGE_SIZE:
  395. ('GET', '/session/$sessionId/session_storage/size'),
  396. Command.GET_LOG:
  397. ('POST', '/session/$sessionId/log'),
  398. Command.GET_AVAILABLE_LOG_TYPES:
  399. ('GET', '/session/$sessionId/log/types'),
  400. Command.CURRENT_CONTEXT_HANDLE:
  401. ('GET', '/session/$sessionId/context'),
  402. Command.CONTEXT_HANDLES:
  403. ('GET', '/session/$sessionId/contexts'),
  404. Command.SWITCH_TO_CONTEXT:
  405. ('POST', '/session/$sessionId/context'),
  406. }
  407. def execute(self, command, params):
  408. """
  409. Send a command to the remote server.
  410. Any path subtitutions required for the URL mapped to the command should be
  411. included in the command parameters.
  412. :Args:
  413. - command - A string specifying the command to execute.
  414. - params - A dictionary of named parameters to send with the command as
  415. its JSON payload.
  416. """
  417. command_info = self._commands[command]
  418. assert command_info is not None, 'Unrecognised command %s' % command
  419. data = utils.dump_json(params)
  420. path = string.Template(command_info[1]).substitute(params)
  421. url = '%s%s' % (self._url, path)
  422. return self._request(command_info[0], url, body=data)
  423. def _request(self, method, url, body=None):
  424. """
  425. Send an HTTP request to the remote server.
  426. :Args:
  427. - method - A string for the HTTP method to send the request with.
  428. - url - A string for the URL to send the request to.
  429. - body - A string for request body. Ignored unless method is POST or PUT.
  430. :Returns:
  431. A dictionary with the server's parsed JSON response.
  432. """
  433. LOGGER.debug('%s %s %s' % (method, url, body))
  434. parsed_url = parse.urlparse(url)
  435. headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
  436. if self.keep_alive:
  437. if body and method != 'POST' and method != 'PUT':
  438. body = None
  439. try:
  440. self._conn.request(method, parsed_url.path, body, headers)
  441. resp = self._conn.getresponse()
  442. except (httplib.HTTPException, socket.error):
  443. self._conn.close()
  444. raise
  445. statuscode = resp.status
  446. else:
  447. password_manager = None
  448. if parsed_url.username:
  449. netloc = parsed_url.hostname
  450. if parsed_url.port:
  451. netloc += ":%s" % parsed_url.port
  452. cleaned_url = parse.urlunparse((
  453. parsed_url.scheme,
  454. netloc,
  455. parsed_url.path,
  456. parsed_url.params,
  457. parsed_url.query,
  458. parsed_url.fragment))
  459. password_manager = url_request.HTTPPasswordMgrWithDefaultRealm()
  460. password_manager.add_password(None,
  461. "%s://%s" % (parsed_url.scheme, netloc),
  462. parsed_url.username,
  463. parsed_url.password)
  464. request = Request(cleaned_url, data=body.encode('utf-8'), method=method)
  465. else:
  466. request = Request(url, data=body.encode('utf-8'), method=method)
  467. for key, val in headers.items():
  468. request.add_header(key, val)
  469. if password_manager:
  470. opener = url_request.build_opener(url_request.HTTPRedirectHandler(),
  471. HttpErrorHandler(),
  472. url_request.HTTPBasicAuthHandler(password_manager))
  473. else:
  474. opener = url_request.build_opener(url_request.HTTPRedirectHandler(),
  475. HttpErrorHandler())
  476. resp = opener.open(request, timeout=self._timeout)
  477. statuscode = resp.code
  478. if not hasattr(resp, 'getheader'):
  479. if hasattr(resp.headers, 'getheader'):
  480. resp.getheader = lambda x: resp.headers.getheader(x)
  481. elif hasattr(resp.headers, 'get'):
  482. resp.getheader = lambda x: resp.headers.get(x)
  483. data = resp.read()
  484. try:
  485. if 300 <= statuscode < 304:
  486. return self._request('GET', resp.getheader('location'))
  487. body = data.decode('utf-8').replace('\x00', '').strip()
  488. if 399 < statuscode <= 500:
  489. return {'status': statuscode, 'value': body}
  490. content_type = []
  491. if resp.getheader('Content-Type') is not None:
  492. content_type = resp.getheader('Content-Type').split(';')
  493. if not any([x.startswith('image/png') for x in content_type]):
  494. try:
  495. data = utils.load_json(body.strip())
  496. except ValueError:
  497. if 199 < statuscode < 300:
  498. status = ErrorCode.SUCCESS
  499. else:
  500. status = ErrorCode.UNKNOWN_ERROR
  501. return {'status': status, 'value': body.strip()}
  502. assert type(data) is dict, (
  503. 'Invalid server response body: %s' % body)
  504. # Some of the drivers incorrectly return a response
  505. # with no 'value' field when they should return null.
  506. if 'value' not in data:
  507. data['value'] = None
  508. return data
  509. else:
  510. data = {'status': 0, 'value': body.strip()}
  511. return data
  512. finally:
  513. LOGGER.debug("Finished Request")
  514. resp.close()