requests.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. ########################################################################
  2. # Searx-Qt - Lightweight desktop application for Searx.
  3. # Copyright (C) 2020-2022 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. Success = 0
  34. HttpError = 1
  35. ConnectionError = 2
  36. Timeout = 3
  37. WrongStatus = 4
  38. DecodeError = 5
  39. NoResults = 6
  40. ProxyError = 7
  41. SSLError = 8
  42. WrongJsonStruct = 9
  43. Other = 10
  44. ErrorTypeStr = {
  45. ErrorType.Success: "Success",
  46. ErrorType.HttpError: "HttpError",
  47. ErrorType.ConnectionError: "ConnectionError",
  48. ErrorType.Timeout: "Timeout",
  49. ErrorType.WrongStatus: "WrongStatus",
  50. ErrorType.DecodeError: "DecodeError",
  51. ErrorType.NoResults: "NoResults",
  52. ErrorType.ProxyError: "ProxyError",
  53. ErrorType.SSLError: "SSLError",
  54. ErrorType.WrongJsonStruct: "WrongJsonStruct",
  55. ErrorType.Other: "Other"
  56. }
  57. class Result:
  58. def __init__(self, url, response, err="", errType=ErrorType.Success, acceptCodes=None):
  59. self._url = url # url used for request.
  60. self._response = response
  61. self._err = err
  62. self._errType = errType
  63. acceptCodes = acceptCodes
  64. if not acceptCodes:
  65. acceptCodes = [200]
  66. if errType == ErrorType.Success and response.status_code not in acceptCodes:
  67. self._errType = ErrorType.WrongStatus
  68. self._err = "WrongStatus: {0}".format(self._response.status_code)
  69. else:
  70. self.verifyFurther()
  71. def __bool__(self):
  72. return not self.failed()
  73. def url(self):
  74. return self._url
  75. def errorType(self): return self._errType
  76. def error(self): return self._err
  77. def content(self):
  78. """ In case json.loads failed and we want to debug.
  79. """
  80. if self._response is None:
  81. return b''
  82. return self._response.content
  83. def text(self):
  84. if self._response is None:
  85. return ''
  86. return self._response.text
  87. def failed(self):
  88. if self._errType is not ErrorType.Success:
  89. return True
  90. return False
  91. def statusCode(self):
  92. if self._response is not None:
  93. return self._response.status_code
  94. return 0
  95. def verifyFurther(self):
  96. pass
  97. class JsonResult(Result):
  98. ExpectedStructure = {}
  99. def __init__(self, url, response, err="", errType=ErrorType.Success, acceptCodes=None):
  100. Result.__init__(
  101. self,
  102. url,
  103. response,
  104. err=err,
  105. errType=errType,
  106. acceptCodes=acceptCodes
  107. )
  108. def verifyFurther(self):
  109. try:
  110. self.json()
  111. except json.JSONDecodeError as err:
  112. self._errType = ErrorType.DecodeError
  113. self._err = "DecodeError: `{0}`".format(err)
  114. except UnicodeDecodeError as err:
  115. # This could happen when the response encoding isn't plain ? (gzip)
  116. # Or we just have malformed data/crap.
  117. self._errType = ErrorType.DecodeError
  118. self._err = "DecodeError: `{0}`".format(err)
  119. # Verify the json structure itself.
  120. verified, error = jsonVerify.verifyStructure(
  121. self.ExpectedStructure,
  122. self.json(),
  123. path=str(self.__class__.__name__) + ".json()"
  124. )
  125. if not verified:
  126. self._errType = ErrorType.WrongJsonStruct
  127. self._err = "WrongJsonStruct: `{0}`".format(error)
  128. def json(self):
  129. if self.errorType() != ErrorType.Success:
  130. return {}
  131. return json.loads(self._response.content)
  132. class RequestsHandler:
  133. def __init__(self, settings):
  134. """
  135. @param settings: TODO don't use Qt stuff in core.
  136. @type settings: searxqt.models.RequestSettingsModel
  137. """
  138. self._settings = settings
  139. def failSafeRequestFactory(func):
  140. def failSafeRequest(self, url, data=None, ResultType=None):
  141. response = None
  142. err = ""
  143. errType = ErrorType.Success
  144. if not ResultType:
  145. # When 'ResultType' isn't specified, set 'JsonResult' as
  146. # default.
  147. ResultType = JsonResult
  148. log.debug("<NEW Request>", self)
  149. log.debug("# ------------------------", self)
  150. log.debug("# ResultType : {0}".format(ResultType), self)
  151. """
  152. Request exceptions
  153. https://docs.python-requests.org/en/master/_modules/requests/exceptions/
  154. """
  155. try:
  156. response = func(self, url, data=data, **self._settings.kwargs)
  157. except HTTPError as e:
  158. # HTTPError is subclass of RequestException
  159. log.debug("Request failed! HTTPError: {0}".format(e), self)
  160. errType = ErrorType.HttpError
  161. err = str(e)
  162. except Timeout as e:
  163. # Timeout is subclass of RequestException
  164. log.debug("Request failed! Timeout: {0}".format(e), self)
  165. errType = ErrorType.Timeout
  166. err = str(e)
  167. except ProxyError as e:
  168. # ProxyError is subclass of ConnectionError
  169. log.debug("Request failed! ProxyError: {0}".format(e), self)
  170. errType = ErrorType.ProxyError
  171. err = str(e)
  172. except SSLError as e:
  173. # SSLError is subclass of ConnectionError
  174. log.debug("Request failed! SSLError: {0}".format(e), self)
  175. errType = ErrorType.SSLError
  176. err = str(e)
  177. except ConnectionError as e:
  178. # ConnectionError is subclass of RequestException
  179. log.debug(
  180. "Request failed! ConnectionError: {0}".format(e),
  181. self
  182. )
  183. errType = ErrorType.ConnectionError
  184. err = str(e)
  185. except RequestException as e:
  186. # This should catch all other
  187. log.debug(
  188. "Request failed! RequestException: {0}".format(e),
  189. self
  190. )
  191. errType = ErrorType.Other
  192. err = str(e)
  193. log.debug("# ------------------------\n", self)
  194. return ResultType(url, response, err=err, errType=errType)
  195. return failSafeRequest
  196. @failSafeRequestFactory
  197. def get(self, url, data=None, ResultType=None, **settingsKwargs):
  198. log.debug("# Type : GET", self)
  199. log.debug("# URL : {0}".format(url), self)
  200. log.debug("# Data : {0}".format(data), self)
  201. log.debug("# Kwargs : {0}".format(settingsKwargs), self)
  202. return requests.get(url, data=data, **settingsKwargs)
  203. @failSafeRequestFactory
  204. def post(self, url, data=None, ResultType=None, **settingsKwargs):
  205. log.debug("# Type : POST", self)
  206. log.debug("# URL : {0}".format(url), self)
  207. log.debug("# Data : {0}".format(data), self)
  208. log.debug("# Kwargs : {0}".format(settingsKwargs), self)
  209. return requests.post(url, data=data, **settingsKwargs)