Сеть. Простое
веб-приложение
Tinkoff Python
Лекция 3
Пара слов обо мне
занимаюсь чат-ботами
Палий Антон
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7105419/tuple.png)
Тест
С чего начинается
веб-запрос?
Пара слов про транспортный уровень
TCP/UDP
TCP | UDP |
---|---|
Transmission | User |
Control | Datagram |
Protocol | Protocol |
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7071795/pasted-from-clipboard.png)
Прикладной уровень
DNS
tinkoff.ru -> 178.248.236.218
HTTP
Hypertext Transfer Protocol
~$ telnet google.com 80 1 ↵
Trying 64.233.165.139...
Connected to google.com.
Escape character is '^]'.
GET /
HTTP/1.0 200 OK
Date: Sat, 22 Feb 2020 18:19:03 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-...
~$ telnet tinkoff.ru 80 ░▒▓ ✔
Trying 178.248.236.218...
Connected to tinkoff.ru.
Escape character is '^]'.
GET /
Connection closed by foreign host.
Что же пошло не так?
HTTPS
HTTP, в котором данные шифруются
Заголовки
key: value
Пара ключ-значения для передачи дополнительной инфомации между клиентом и сервером
- General Headers— есть в любом сообщении клиента и сервера.
- Request Headers — только в запросах клиента.
- Response Headers — только для ответов от сервера.
- Entity Headers— сопровождают каждую сущность сообщения.
404
слайд не найден
200
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7071960/httpstatuses.png)
-
1×× Informational
-
2×× Success
- 200 OK
3×× Redirection
- 301 Moved Permanently
- 307 Temporary Redirect
- 308 Permanent Redirect
from http import HTTPStatus
Как из программы получить доступ к сетевому интерфейсу?
Socket
Общее
- Создание
- Отправка
- Получение
- Закрытие
Клиент
- Подключиться
Сервер
- Бинд
- Слушать
- Подтвердить
import socket
HOST = '127.0.0.1'
PORT = 1025
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
~$ telnet localhost 1025
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /
GET /
Программное выполнение запросов
curl
Консольная утилита для выполнения запросов.
~$ curl -i tinkoff.ru 2 ↵ flask-testing-mExZZ2-q-py3.7
HTTP/1.1 301 Moved Permanently
Server: QRATOR
Date: Mon, 24 Feb 2020 16:03:37 GMT
Content-Length: 0
Connection: keep-alive
Keep-Alive: timeout=15
Location: https://tinkoff.ru/
Позволяет | Не позволяет |
---|---|
Делать http запросы | работать из python |
Подключаться по https | |
Удобный для консоли синтаксис | |
Действие в одну команду |
urllib.request
Стандартное решение из коробки
In [1]: import urllib
In [2]: f = urllib.request.urlopen('https://tinkoff.ru/')
In [3]: f
Out[3]: <http.client.HTTPResponse at 0x7f461c847d50>
In [4]: print(f.read(100).decode('utf-8'))
<!DOCTYPE html>
<html class="no-js" lang="ru">
<head>
<meta charset="UTF-8">
<title data-meta-dynami
При работе имеет множество ньюансов, да и в своей же документации рекомендует использовать
Requests
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7102014/pasted-from-clipboard.png)
In [1]: import requests
In [2]: from http import HTTPStatus
In [3]: r = requests.get('https://tinkoff.ru/')
In [4]: r.status_code
Out[4]: 200
In [5]: assert r.status_code == HTTPStatus.OK
In [5]: r.headers
Out[5]: {'Date': 'Tue, 25 Feb 2020 08:30:31 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'x-xss-protection': '1', 'x-frame-options': 'sameorigin', 'content-security-policy': "connect-src sync.datamind.ru dpm.demdex.net tinkoffcreditsystems.d3.sc.omtrdc.net assets.adobedtm.com *.omniture.com *.g.doubleclick.net geocode-maps.yandex.ru/1.x/ *.google-analytics.com *.datamind.ru www.cdn-tinkoff.ru 'self' *.tinkoff.ru *.tcsbank.ru www.tinkoff.ru cfg.tinkoff.ru api.tinkoff.ru acdn.tinkoff.ru business.tinkoff.ru; script-src sync.datamind.ru www.google-analytics.com www.googletagmanager.com www.googleadservices.com *.g.doubleclick.net assets.adobedtm.com dpm.demdex.net tinkoffcreditsystems.d3.sc.omtrdc.net *.omniture.com *.google-analytics.com googleads.g.doubleclick.net www.google.com www.google.ru connect.ok.ru vk.com *.datamind.ru s.ytimg.com connect.facebook.net *.tinkoff.ru *.tcsbank.ru www.cdn-tinkoff.ru 'self' 'unsafe-eval' 'unsafe-inline'; img-src *.datamind.ru dpm.demdex.net www.google-analytics.com tinkoffcreditsystems.d3.sc.omtrdc.net cm.everesttech.net stats.g.doubleclick.net dp.adsdata.ru www.google.com www.google.ru top-fwz1.mail.ru vk.com login.vk.com mc.yandex.ru ad.mail.ru adfocus.ru www.facebook.com ad.doubleclick.net *.g.doubleclick.net www.googleadservices.com *.google.com *.yandex.ru *.yandex.net *.2o7.net *.demdex.net cx.atdmt.com analytics.twitter.com t.co eu-sonar.sociomantic.com *.sravni.ru www.banki.ru *.pool.datamind.ru statad.ru googletagmanager.com www.facebook.com/tr/ www.cdn-tinkoff.ru *.tinkoff.ru manalyticshub.com 'self' data: *.tcsbank.ru; frame-src *.tinkoff.demdex.net *.omniture.com bid.g.doubleclick.net www.facebook.com *.demdex.net vk.com static.datamind.ru platform.twitter.com connect.ok.ru *.datamind.ru www.cdn-tinkoff.ru 'self' *.tinkoff.ru *.tcsbank.ru; report-uri https://www.tinkoff.ru/api/front/log/csp-error; default-src 'self' www.cdn-tinkoff.ru *.tinkoff.ru data:; style-src 'unsafe-inline' 'self' *.tinkoff.ru *.tcsbank.ru www.cdn-tinkoff.ru", 'expires': '0', 'pragma': 'no-cache', 'cache-control': 'no-cache, no-store, must-revalidate', 'set-cookie': 'dco.id=8da2752b-1ed0-4751-b6ce-0fb74d9c0b94; Path=/; Expires=Thu, 25 Feb 2021 08:30:31 GMT; SameSite=Lax', 'x-envoy-upstream-service-time': '154', 'vary': 'Accept-Encoding', 'content-encoding': 'gzip', 'Server': 'MSX Turbo R (R900) Web Server 1.13', 'X-Powered-By': 'MSX Basic Web Clustered Engine (WCE pi) - NG'}
In [6]: r.text
Out[6]: '<!DOCTYPE html>\n<html class="no-js" lang="ru">\n<head>\n<meta charset="UTF-8">\n<title data-meta-dynamic="true">Тинькофф — Кредитные и дебетовые карт
timeout
всегда указывайте явно
In [1]: import requests
In [2]: r = requests.get('https://tinkoff.ru/')
In [3]: r
Out[3]: <Response [200]>
In [3]: r = requests.get('https://tinkoff.ru/', timeout=0.01)
---------------------------------------------------------------------------
timeout Traceback (most recent call last)
Перерыв
Объединим сеть и сокет
Выберем фреймфорк
- Django
- Flask
- Sanic
- FastAPI
- Bottle
- Falcon
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7101540/pasted-from-clipboard.png)
Напишем простое приложение
from flask import Flask
app = Flask(__name__)
@app.route('/')
def main():
return "kek"
FLASK_APP=app.py PYTHONPATH=$(pwd)/app_module flask run
* Tip: There are .env or .flaskenv files present. Do "pip install python-dotenv" to use them.
* Serving Flask app "app.py"
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
telnet localhost 5000
Connected to localhost.
Escape character is '^]'.
GET /
kekConnection closed by foreign host.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7101628/pasted-from-clipboard.png)
Добавим немного красоты
# добавим h1 тег к нашему тексту
from flask import Flask
app = Flask(__name__)
@app.route('/')
def main():
return "<h1>lol</h1>"
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7101655/pasted-from-clipboard.png)
Тесты
import pytest
from app import app
@pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_connection(client):
result = client.get('/')
assert result.status == '200 OK'
assert result.data == b'<h1>lol</h1>'
from flask import Flask
app = Flask(__name__)
@app.route('/')
def main():
return "kek"
FLASK_APP=app.py PYTHONPATH=$(pwd)/app_module flask run
* Tip: There are .env or .flaskenv files present. Do "pip install python-dotenv" to use them.
* Serving Flask app "app.py"
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
WSGI
Стандарт взаимодействия для веб-серверов типа nginx и веб-приложений на языке python.
Является низкоуровневым описанием работы приложения.
-
должно быть вызываемым (callable) объектом (обычно это функция или метод)
-
принимать два параметра:
-
словарь переменных окружения (environ)
-
обработчик запроса (start_response)
-
-
вызывать обработчик запроса с кодом HTTP-ответа и HTTP-заголовками
-
возвращать итерируемый объект с телом ответа
HELLO_WORLD = b"Hello world!\n"
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [HELLO_WORLD]
if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('localhost', 8080, simple_app)
srv.serve_forever()
Gunicorn
Web Server Gateway Interface HTTP server
Gunicorn решает несколько проблем
- Запуск приложения
- Масштабирование приложения
- Конфигурирование приложения
- Балансировка трафика
- Контроль приложения (Перезагрузки, размер очереди и пр)
- SSL
- Обработка сигналов
- Работа с веб серверами
- и пр
gunicorn \
--workers 4 \
--timeout 2000 \
--max-requests 100000 \
--bind 0.0.0.0:5000 \
app_module:app()
Напишем приложение посложнее
HTML
from uuid import uuid4
from flask import Flask
app = Flask(__name__)
response_base_body = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{}
</body>
</html>"""
@app.route('/')
def main():
return response_base_body.format(uuid4())
Теперь мы отдаём правильный HTML, но красивый ли код?
Файлы?
Модули?
Шаблоны!
jinja
- Автоматизировать работу с шаблонами
- Автоматически избегать XSS инъекции
- Наследовать шаблоны
- Шаблозизировать
- Избавлять код от html
Jinja позволяет
├── project
│ └── app.py
└── templates
└── base.html
from uuid import uuid4
from flask import Flask, render_template
server = Flask('some_name')
@server.route('/')
def main():
return render_template('base.html', data=uuid4())
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{{ data }}
</body>
</html>
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7105138/pasted-from-clipboard.png)
Text
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7102540/pasted-from-clipboard.png)
Что-то сложнее
├── project
│ └── app.py
└── templates
└── base.html
@server.route('/')
def main():
users = {}
for i in range(100):
users[i] = uuid4()
return render_template('base.html', users=users)
<body>
<table style="width:100%">
<tr>
<th>id</th>
<th>data</th>
</tr>
{% for user_id, user_name in users.items() %}
<tr>
<th> {{ user_id }} </th>
<th> {{ user_name }} </th>
</tr>
{% endfor %}
</table>
</body>
{% ... %} Выражения
{{ ... }} Вывод значения
{# ... #} Комментарии
Основные разделители в jinja
Выражения
{% for var in vars %}
{% endfor %}
{% if a == '' %}
{% endif %}
{% macro input(name) -%}
<div> "{{ name }}" </div>
{%- endmacro %}
{% extends "base.html" %}
{% block title %}some data{% endblock %}
Вывод значения
{{ name }}
{{ 1 + 1 }}
{{ var is some_test }}
{% if variable is defined %}
{{ name | filter }}
{{ my_variable|default('kek') }}
Линтеры
Flake8
$ flake8 path/to/code/
app/__init__.py:1:1: W391 blank line at end of file
app/utils.py:7:2: E225 missing whitespace around operator
app/utils.py:10:1: E402 module level import not at top of file
app/utils.py:10:1: F401 'os' imported but unused
app/__main__.py:1:1: F401 '.utils' imported but unused
Flake8 plugins
- flake8-builtins
- flake8-comprehensions
- flake8-eradicate
- flake8-isort
- flake8-logging-format
- flake8-pytest
- pep8-naming
- etc.
Pylint
$ pylint path/to/code
************* Module app.utils
app/utils.py:7:1: C0326: Exactly one space required around assignment
x=2
^ (bad-whitespace)
app/utils.py:1:0: C0111: Missing module docstring (missing-docstring)
app/utils.py:5:0: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
app/utils.py:7:0: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
app/utils.py:10:0: C0413: Import "import os" should be placed at the top of the module (wrong-import-position)
app/utils.py:10:0: W0611: Unused import os (unused-import)
# my_strange_module.py
# pylint:disable=missing-docstring
import os # noqa: F401
Отключение проверок
Настройки линтеров
[flake8]
enable-extensions = G
exclude = .git
ignore =
A003 ; 'id' is a python builtin, consider renaming the class attribute
W504 ; Line break occurred after a binary operator
max-complexity = 20
max-line-length = 80
show-source = true
[pylint] ; `pylint --rcfile=setup.cfg my_code`
good-names=i,j,k,e,x,_,pk,id
max-module-lines=300
output-format = colorized
disable=
C0103, ; Constant name "api" doesn't conform to UPPER_CASE naming style (invalid-name)
C0111, ; Missing module docstring (missing-docstring)
C0330, ; Wrong hanging indentation before block (add 4 spaces)
E0213, ; Method should have "self" as first argument (no-self-argument) - N805 for flake8
R0201, ; Method could be a function (no-self-use)
R0901, ; Too many ancestors (m/n) (too-many-ancestors)
R0903, ; Too few public methods (m/n) (too-few-public-methods)
W0511, ; TODO needed? (fixme)
setup.cfg
Автоформатирование кода
Black
# BEFORE
x = { 'a':37,'b':42,
'c':927}
y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo ( object ):
def f (self ):
return 37*-+2
def g(self, x,y=42):
return y
def f ( a ) :
return 37+-+a[42-x : y**3]
# AFTER
x = {"a": 37, "b": 42, "c": 927}
y = "hello " "world"
z = "hello " + "world"
a = "hello {}".format("world")
class foo(object):
def f(self):
return 37 * -+2
def g(self, x, y=42):
return y
def f(a):
return 37 + -+a[42 - x : y ** 3]
$ black app
Isort
$ isort --apply --recursive app
...
from my_lib import Object
print("Hey")
import os
from my_lib import Object3
from my_lib import Object2
import sys
from third_party import libA
import sys
from third_party import libB
print("yo")
import os
import sys
from third_party import libA, libB
from my_lib import Object, Object2, Object3
print("Hey")
print("yo")
Работа с зависимостями
![](https://s3.amazonaws.com/media-p.slid.es/uploads/1013370/images/7104571/poetry.png)
![](https://raw.githubusercontent.com/python-poetry/poetry/master/assets/install.gif)
Tinkoff Python 2020 - 3
By Afonasev Evgeniy
Tinkoff Python 2020 - 3
- 361