requests.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. ########################################################################
  2. # Searx-qt - Lightweight desktop application for SearX.
  3. # Copyright (C) 2020 CYBERDEViL
  4. #
  5. # This file is part of Searx-qt.
  6. #
  7. # Searx-qt is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Searx-qt is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. #
  20. ########################################################################
  21. import requests
  22. from requests.exceptions import (
  23. HTTPError,
  24. ConnectionError,
  25. Timeout,
  26. ProxyError,
  27. SSLError,
  28. RequestException
  29. )
  30. import json
  31. from searxqt.core import jsonVerify, log
  32. class ErrorType:
  33. HttpError = 0
  34. ConnectionError = 1
  35. Timeout = 3
  36. WrongStatus = 4
  37. DecodeError = 5
  38. NoResults = 6
  39. ProxyError = 7
  40. SSLError = 8
  41. WrongJsonStruct = 9
  42. Other = 10
  43. ErrorTypeStr = {
  44. ErrorType.HttpError: "HttpError",
  45. ErrorType.ConnectionError: "ConnectionError",
  46. ErrorType.Timeout: "Timeout",
  47. ErrorType.WrongStatus: "WrongStatus",
  48. ErrorType.DecodeError: "DecodeError",
  49. ErrorType.NoResults: "NoResults",
  50. ErrorType.ProxyError: "ProxyError",
  51. ErrorType.SSLError: "SSLError",
  52. ErrorType.WrongJsonStruct: "WrongJsonStruct",
  53. ErrorType.Other: "Other"
  54. }
  55. class Result:
  56. def __init__(self, url, response, err="", errType=None, acceptCodes=None):
  57. self._url = url # url used for request.
  58. self._response = response
  59. self._err = err
  60. self._errType = errType
  61. acceptCodes = acceptCodes
  62. if not acceptCodes:
  63. acceptCodes = [200]
  64. if errType is None and response.status_code not in acceptCodes:
  65. self._errType = ErrorType.WrongStatus
  66. self._err = "WrongStatus: {0}".format(self._response.status_code)
  67. else:
  68. self.verifyFurther()
  69. def __bool__(self):
  70. return not self.failed()
  71. def url(self):
  72. return self._url
  73. def errorType(self): return self._errType
  74. def error(self): return self._err
  75. def content(self):
  76. """ In case json.loads failed and we want to debug.
  77. """
  78. if self._response is None:
  79. return b''
  80. return self._response.content
  81. def text(self):
  82. if self._response is None:
  83. return ''
  84. return self._response.text
  85. def failed(self):
  86. if self._errType is not None:
  87. return True
  88. return False
  89. def statusCode(self):
  90. if self._response is not None:
  91. return self._response.status_code
  92. return 0
  93. def verifyFurther(self):
  94. pass
  95. class JsonResult(Result):
  96. ExpectedStructure = {}
  97. def __init__(self, url, response, err="", errType=None, acceptCodes=None):
  98. Result.__init__(
  99. self,
  100. url,
  101. response,
  102. err=err,
  103. errType=errType,
  104. acceptCodes=acceptCodes
  105. )
  106. def verifyFurther(self):
  107. try:
  108. self.json()
  109. except json.JSONDecodeError as err:
  110. self._errType = ErrorType.DecodeError
  111. self._err = "DecodeError: `{0}`".format(err)
  112. except UnicodeDecodeError as err:
  113. # This could happen when the response encoding isn't plain ? (gzip)
  114. # Or we just have malformed data/crap.
  115. self._errType = ErrorType.DecodeError
  116. self._err = "DecodeError: `{0}`".format(err)
  117. # Verify the json structure itself.
  118. verified, error = jsonVerify.verifyStructure(
  119. self.ExpectedStructure,
  120. self.json(),
  121. path=str(self.__class__.__name__) + ".json()"
  122. )
  123. if not verified:
  124. self._errType = ErrorType.WrongJsonStruct
  125. self._err = "WrongJsonStruct: `{0}`".format(error)
  126. def json(self):
  127. if self.errorType() is not None:
  128. return {}
  129. return json.loads(self._response.content)
  130. class RequestsHandler:
  131. def __init__(self, settings):
  132. """
  133. @param settings: TODO don't use Qt stuff in core.
  134. @type settings: searxqt.models.RequestSettingsModel
  135. """
  136. self._settings = settings
  137. def failSafeRequestFactory(func):
  138. def failSafeRequest(self, url, data=None, ResultType=None):
  139. response = None
  140. err = ""
  141. errType = None
  142. if not ResultType:
  143. # When 'ResultType' isn't specified, set 'JsonResult' as
  144. # default.
  145. ResultType = JsonResult
  146. log.debug("<NEW Request>", self)
  147. log.debug("# ------------------------", self)
  148. log.debug("# ResultType : {0}".format(ResultType), self)
  149. """
  150. Request exceptions
  151. https://docs.python-requests.org/en/master/_modules/requests/exceptions/
  152. """
  153. try:
  154. response = func(self, url, data=data, **self._settings.kwargs)
  155. except HTTPError as e:
  156. # HTTPError is subclass of RequestException
  157. log.debug("Request failed! HTTPError: {0}".format(e), self)
  158. errType = ErrorType.HttpError
  159. err = e
  160. except Timeout as e:
  161. # Timeout is subclass of RequestException
  162. log.debug("Request failed! Timeout: {0}".format(e), self)
  163. errType = ErrorType.Timeout
  164. err = e
  165. except ProxyError as e:
  166. # ProxyError is subclass of ConnectionError
  167. log.debug("Request failed! ProxyError: {0}".format(e), self)
  168. errType = ErrorType.ProxyError
  169. err = e
  170. except SSLError as e:
  171. # SSLError is subclass of ConnectionError
  172. log.debug("Request failed! SSLError: {0}".format(e), self)
  173. errType = ErrorType.SSLError
  174. err = e
  175. except ConnectionError as e:
  176. # ConnectionError is subclass of RequestException
  177. log.debug(
  178. "Request failed! ConnectionError: {0}".format(e),
  179. self
  180. )
  181. errType = ErrorType.ConnectionError
  182. err = e
  183. except RequestException as e:
  184. # This should catch all other
  185. log.debug(
  186. "Request failed! RequestException: {0}".format(e),
  187. self
  188. )
  189. errType = ErrorType.Other
  190. err = e
  191. log.debug("# ------------------------\n", self)
  192. return ResultType(url, response, err=err, errType=errType)
  193. return failSafeRequest
  194. @failSafeRequestFactory
  195. def get(self, url, data=None, ResultType=None, **settingsKwargs):
  196. log.debug("# Type : GET", self)
  197. log.debug("# URL : {0}".format(url), self)
  198. log.debug("# Data : {0}".format(data), self)
  199. log.debug("# Kwargs : {0}".format(settingsKwargs), self)
  200. return requests.get(url, data=data, **settingsKwargs)
  201. @failSafeRequestFactory
  202. def post(self, url, data=None, ResultType=None, **settingsKwargs):
  203. log.debug("# Type : POST", self)
  204. log.debug("# URL : {0}".format(url), self)
  205. log.debug("# Data : {0}".format(data), self)
  206. log.debug("# Kwargs : {0}".format(settingsKwargs), self)
  207. return requests.post(url, data=data, **settingsKwargs)