Advertisement
stuppid_bot

Untitled

Mar 10th, 2016
246
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.72 KB | None | 0 0
  1. from PyQt5.QtCore import (
  2.     QEventLoop,
  3.     QObject,
  4.     QUrl
  5. )
  6. from PyQt5.QtNetwork import (
  7.     QNetworkAccessManager,
  8.     QNetworkReply,
  9.     QNetworkRequest
  10. )
  11. from urllib.parse import urlencode, parse_qsl
  12. import io
  13. import json
  14. import logging
  15. import mimetypes
  16. import os
  17. import re
  18. import uuid
  19.  
  20. log = logging.getLogger(__name__)
  21.  
  22. RE_HEADER = re.compile("[a-z]+(-[a-z]+)*$", re.I)
  23.  
  24.  
  25. class Client(QObject):
  26.     def __init__(self, networkManager=None):
  27.         super().__init__()
  28.         self.networkManager = networkManager or QNetworkAccessManager(self)
  29.         # Опциии
  30.         # Максимально допустимое количество редиректов
  31.         self.maxRedirects = 3
  32.         # Автоматически переходить по ссылке при перенаправлении
  33.         self.followLocation = True
  34.         # Подставлять referer при редиректе
  35.         self.autoReferer = True
  36.         self._headers = HttpHeaders({
  37.             'user-agent': 'Mozilla 5.0 (PyQt5 Client)'
  38.         })
  39.  
  40.     @property
  41.     def headers(self):
  42.         return self._headers
  43.  
  44.     @headers.setter
  45.     def headers(self, value):
  46.         if not isinstance(value, HttpHeaders):
  47.             value = HttpHeaders(value)
  48.         self._headers = value
  49.  
  50.     def request(self,
  51.                 url,
  52.                 params={},
  53.                 data={},
  54.                 files={},
  55.                 headers={},
  56.                 encoding="utf-8",  # Кодировка страницы. В requests нет
  57.                 _history=[]):
  58.         url = QUrl(url)
  59.         defaults = self.headers.copy()
  60.         defaults.update(headers)
  61.         headers = defaults
  62.         if params:
  63.             q = url.query()
  64.             q = parse_qsl(q, encoding=encoding)
  65.             q = dict(q)
  66.             q.update(params)
  67.             q = urlencode(q, encoding=encoding)
  68.             url.setQuery(q)
  69.         if data or files:
  70.             if files:
  71.                 boundary = uuid.uuid4().hex
  72.                 delim = "--{}\r\n".format(boundary).encode('ascii')
  73.                 end = "--{}--\r\n".format(boundary).encode('ascii')
  74.                 buf = io.BytesIO()
  75.                 for k, v in data.items():
  76.                     buf.write(delim)
  77.                     buf.write(
  78.                         ('Content-Disposition: form-data;'
  79.                          ' name="{}"\r\n\r\n').format(k).encode(encoding)
  80.                     )
  81.                     buf.write(str(v).encode(encoding))
  82.                     buf.write(b"\r\n")
  83.                 for name, value in files.items():
  84.                     if isinstance(value, (list, tuple)):
  85.                         # ('filename', b'<binary_data>')
  86.                         filename, content = value
  87.                         if not isinstance(content, (bytearray, bytes)):
  88.                             content = str(content).encode('u8')
  89.                     else:
  90.                         filename = value
  91.                         content = open(filename, 'rb').read()
  92.                     buf.write(delim)
  93.                     buf.write(
  94.                         ('Content-Disposition: form-data;'
  95.                          ' name="{}";'
  96.                          ' filename="{}"\r\n'
  97.                          'Content-Type: {}\r\n\r\n').format(
  98.                             name,
  99.                             os.path.basename(filename),
  100.                             (mimetypes.guess_type(filename)[0] or
  101.                                 "application/octet-stream")
  102.                         ).encode(encoding)
  103.                     )
  104.                     buf.write(content)
  105.                     buf.write(b"\r\n")
  106.                 buf.write(end)
  107.                 data = buf.getvalue()
  108.                 headers['content-type'] = \
  109.                     "multipart/form-data; boundary={}".format(boundary)
  110.             else:
  111.                 data = urlencode(data, encoding=encoding).encode('ascii')
  112.                 headers['content-type'] = "application/x-www-form-urlencoded"
  113.         request = QNetworkRequest(QUrl(url))
  114.         for header, value in headers.items():
  115.             request.setRawHeader(
  116.                 header.encode('ascii'), str(value).encode('ascii'))
  117.         if data:
  118.             reply = self.networkManager.post(request, data)
  119.         else:
  120.             reply = self.networkManager.get(request)
  121.         loop = QEventLoop()
  122.         reply.finished.connect(loop.quit)
  123.         loop.exec_()
  124.         # http://doc.qt.io/qt-4.8/qnetworkreply.html#error
  125.         error = reply.error()
  126.         # http://doc.qt.io/qt-5/qnetworkreply.html#NetworkError-enum
  127.         if (error != QNetworkReply.NoError and
  128.                 # 401 ошибка (возникает при авторизации)
  129.                 error != QNetworkReply.ContentAccessDenied and
  130.                 # 404 тоже содержит контент
  131.                 error != QNetworkReply.ContentNotFoundError):
  132.             # http://doc.qt.io/qt-4.8/qiodevice.html#errorString
  133.             raise NetworkError(reply.errorString())
  134.         response = Response(reply, _history)
  135.         location = response.headers.get("location")
  136.         # На Википедии написано, что вроде как, если метод не является
  137.         # методом HEAD или GET, то пользователь должен подтвердить
  138.         # действие
  139.         if (location and
  140.                 self.followLocation and
  141.                 (300 < response.status <= 308)):
  142.             if len(_history) == self.maxRedirects:
  143.                 raise ClientError(
  144.                     "Max redirects ({}) exceeded".format(self.maxRedirects))
  145.             _history.append(response)
  146.             if self.autoReferer:
  147.                 # Добавляем referer
  148.                 headers["referer"] = response.url
  149.             log.debug("Follow location: %s", location)
  150.             response = self.request(
  151.                 url.resolved(QUrl(location)),
  152.                 headers=headers,
  153.                 _history=_history
  154.             )
  155.         return response
  156.  
  157.     def get(self, url, params={}, **kw):
  158.         return self.request(url, params, **kw)
  159.  
  160.     def post(self, url, data={}, files={}, **kw):
  161.         return self.request(url, data=data, files=files, **kw)
  162.  
  163.  
  164. class JsonObject(dict):
  165.     def __getattr__(self, attr):
  166.         return self[attr]
  167.  
  168.     def __setattr__(self, attr, value):
  169.         self[attr] = value
  170.  
  171.  
  172. class Response:
  173.     def __init__(self, reply, history):
  174.         self.reply = reply
  175.         # self.request = reply.request()
  176.         self.history = list(history)
  177.         self.url = reply.url().toString()
  178.         self.status = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
  179.         self.reason = reply.attribute(
  180.             QNetworkRequest.HttpReasonPhraseAttribute)
  181.         headers = {str(v[0], "ascii"): str(v[1], "ascii")
  182.                    for v in reply.rawHeaderPairs()}
  183.         self.headers = HttpHeaders(headers)
  184.         # media subtypes of the "text" type are defined to have a
  185.         # default charset value of "ISO-8859-1" when received via HTTP
  186.         self.encoding = self.headers.charset or "iso-8859-1"
  187.         self._content = None
  188.  
  189.     @property
  190.     def content(self):
  191.         if self._content is None:
  192.             self._content = self.read()
  193.         return self._content
  194.  
  195.     @property
  196.     def text(self):
  197.         return str(self.content, self.encoding, errors='replace')
  198.  
  199.     def json(self, object_hook=JsonObject, **kw):
  200.         return json.loads(self.text, object_hook=object_hook, **kw)
  201.  
  202.     def read(self, amt=None):
  203.         if amt:
  204.             data = self.reply.read(amt)
  205.         else:
  206.             # Эта вроде возвращает QByteArray, а read просто байты (может баг
  207.             # или так и должно быть)
  208.             data = self.reply.readAll().data()
  209.         return data
  210.  
  211.     def close(self):
  212.         pass
  213.  
  214.     def __repr__(self):
  215.         return "<{} [{}]>".format(self.__class__.__name__, self.status)
  216.  
  217.  
  218. class HttpHeaders(dict):
  219.     def __init__(self, *args, **kwargs):
  220.         super().__init__()
  221.         self.update(*args, **kwargs)
  222.  
  223.     @property
  224.     def contentType(self):
  225.         return self.get("content-type")
  226.  
  227.     @property
  228.     def mimeType(self):
  229.         if self.contentType:
  230.             return self.contentType.split(';')[0]
  231.  
  232.     @property
  233.     def charset(self):
  234.         if self.contentType:
  235.             match = re.search(
  236.                 r";[ \t]+charset=([^;]+)", self.contentType, re.I)
  237.             if match:
  238.                 return match.group(1)
  239.  
  240.     @staticmethod
  241.     def normalize(key):
  242.         return "-".join(map(str.capitalize, key.split("-")))
  243.  
  244.     def get(self, key, default=None):
  245.         return super().get(self.normalize(key), default)
  246.  
  247.     def setdefault(self, key, default=None):
  248.         return super().setdefault(self.normalize(key), default)
  249.  
  250.     def pop(self, key):
  251.         return super().pop(self.normalize(key))
  252.  
  253.     def popitem(self, key):
  254.         return super().popitem(self.normalize(key))
  255.  
  256.     def update(self, *args, **kwargs):
  257.         d = dict(*args, **kwargs)
  258.         for k, v in d.items():
  259.             self[k] = v
  260.  
  261.     def copy(self):
  262.         return self.__class__(self)
  263.  
  264.     def __setitem__(self, name, value):
  265.         if not RE_HEADER.match(name):
  266.             raise TypeError("Bad header name: {}".format(name))
  267.         super().__setitem__(self.normalize(name), value)
  268.  
  269.     def __getitem__(self, key):
  270.         return super().__getitem__(self.normalize(key))
  271.  
  272.     def __delitem__(self, key):
  273.         return super().__delitem__(self.normalize(key))
  274.  
  275.     def __contains__(self, key):
  276.         return super().__contains__(self.normalize(key))
  277.  
  278.  
  279. class ClientError(Exception):
  280.     pass
  281.  
  282.  
  283. class NetworkError(ClientError):
  284.     pass
  285.  
  286.  
  287. if __name__ == '__main__':
  288.     from PyQt5.QtCore import QCoreApplication
  289.     import sys
  290.     logging.basicConfig(level=logging.DEBUG)
  291.     app = QCoreApplication(sys.argv)
  292.     http = Client()
  293.  
  294.     r = http.get("http://httpbin.org/get", {"foo": "bar"})
  295.     print("Get request:")
  296.     print("Url:", r.url)
  297.     print("Code:", r.status)
  298.     print("Message:", r.reason)
  299.     print(r.json().args)
  300.  
  301.     r = http.post("http://httpbin.org/post", {"foo": "bar"})
  302.     print("Post request:")
  303.     print(r.json().form)
  304.  
  305.     r = http.post("http://httpbin.org/post", files={"file": __file__})
  306.     print("File uploading:")
  307.     print(r.json().files)
  308.  
  309.     r = http.get("http://httpbin.org/redirect/3", {"foo": "bar"})
  310.     print(r.text)
  311.     print("History:")
  312.     print("\n".join([v.url for v in r.history]))
  313.     print("Prev history:")
  314.     print("\n".join([v.url for v in r.history[-1].history]))
  315.  
  316.     # app.exec_()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement