+--------------------------+
|.------------------------.|
|| kee_reel@blog:~$ cd    ||
|| си python терминал     ||
|| opengl sql сети        ||
||                        ||
|| мой_проект     обо_мне ||
|.------------------------.|
+-::--------------------::-+
.--------------------------.
 // /ooooooooooooooooooooo\\ \\ 
 // /ooooooooooooooooooooooo\\ \\ 
//------------------------------\\
\\------------------------------//

Python. Основы веб-сервера Flask

Представь, что ты написал скрипт, который может пригодиться кому-то кроме тебя – как им поделиться?

У тебя есть три варианта:

  • Выложить исходный код в открытый доступ (например на GitHub) – для этого пользователь твоей программы должен уметь запускать Python скрипты.
  • Подготовить исполняемый файл (например через PyInstaller) – скрипт будет готов к запуску, и пользователь сможет запустить его как обычное приложение.
  • Развернуть веб-сервер, на котором будет исполняться твоя программа – любой пользователь сможет воспользоваться ей через браузер.

Первые два варианта будут запускаться на машине пользователя, а вот для третьего варианта тебе понадобится сервер.

Веб-сервер идеально подойдёт, если ты хочешь централизованно хранить данные пользователя и иметь над ними полный контроль.

Веб-сервер

Если ты слабо представляешь себе что такое сервер, то обратись к этой статье. В ней я рассказываю всё, что поможет тебе представить полную картину.

Что такое веб-сервер? Это специализированный сервер, который может возвращать пользователю статические или динамические HTML страницы.

  • Статические – эти страницы никак не изменяются сервером, и просто отдаются пользователю (например страницы на википедии – они для всех одинаковые)
  • Динамические – эти страницы изменяются перед отправкой пользователю (например твоя страница в соцсети – она изменяется специально под тебя)

Веб-сервер в Python

В Python есть 2 самых распространённых фреймворка:

  • Django – предоставляет множество функций и подходит для больших проектов
  • Flask – предоставляет минимум функций и подходит для небольших проектов

Фреймворк (framework, каркас) – готовая основа приложения, которую можно легко расширять под свои задачи

В этой статье мы рассмотрим фреймворк Flask, потому что на нём будет проще начать изучение веб-разработки.

Простейший веб-сервер с использованием Flask

Сперва необходимо установить этот фреймворк – выполни в консоли эту команду:

pip install Flask

pip – это инструмент установки сторонних пакетов, встроенный в Python.

После установки Flask, создай скрипт и скопируй туда этот код:

from flask import Flask

app = Flask('my_first_server')

@app.route('/')
def hello_world():
    return 'Hello World'
 
app.run(port=1234)

Всего 6 строчек и веб-сервер готов – вот мощь фреймворков. Минусом же является то, что тебе придётся разбираться в том, как заставить этот фреймворк делать то, что тебе надо.

Сейчас мы разберём что тут написано, но сперва давай проверим что всё работает.

Запусти этот скрипт и открой в браузере адрес: 127.0.0.1:1234

Ты увидишь текст, который возвращается в функции hello_world.

Теперь давай разбираться!

Почему 127.0.0.1? Это IP адрес интерфейса обраной петли (loopback interface) – если ты указываешь этот адрес, то твой компьютер заходит сам на себя. Обычно этот адрес используется при разработке приложения, которое принимает данные по сети (наш случай).

Порт 1234 – это порт, на котором твой скрипт принимает данные по сети (указан в последней строчке). Как ты мог узнать из статьи про сервер, порты от 1024 до 49152 могут использоваться различными серверами – ты сейчас как раз разрабатываешь сервер.

Теперь по коду:

# Подключение пакета flask, и импортирование из него класса Flask
# В классе Flask реализованы все функции веб-сервера
from flask import Flask

# Создаём объект класса Flask - наш веб-сервер
app = Flask('my_first_server')

# Создаём функцию hello_world(), и заворачиваем её в декоратор route с параметром "/"
# Этот декоратор регистрирует в "app" функции, которые будут обрабатывать клиентские
# запросы по указанному пути. В данном случае, если клиент ничего не указывает
# в запросе, то мы вызываем эту функцию.
@app.route('/')
def hello_world():
    return 'Hello World'
 
# Запускаем наш веб-сервер на порте 1234. При вызове run() веб-сервер входит в бесконечный
# цикл, в котором он будет обрабатывать все входящие запросы, и вызвать функции, которые
# мы зарегистрировали через декоратор route.
app.run(port=1234)

Давай добавим обработку нового пути:

from flask import Flask

app = Flask('my_first_server')

@app.route('/')
def hello_world():
    return 'Hello World'

@app.route('/test')
def this_is_test_handler_func():
	return '<H1 color="red">test</H1>'

app.run(port=1234)

Попробуй перезапустить скрипт и зайти на: 127.0.0.1:1234/test

Вот, теперь твой сервер поддерживает несколько разных запросов:

  • Если клиент ничего не указывает после адреса, то мы вернём строку “Hello World”
  • Если клиент указал путь “/test”, то мы верём красную строку “test”, написанную крупным шрифтом

Функция, которая вызывается для различных запросов, называется обработчиком запроса.

Возвращаем HTML страницу

Статическая HTML страница

В папке со своим скриптом создай папку “templates” и создай в ней файл “index.html”.

Скопируй этот текст в этот файл:

<html>
 <body
  <h1>Current date and time: 2021-10-24 15:03:59</h1>
 </body>
</html>

Этими действиями ты создал шаблон HTML файла, который можно теперь отдавать через веб-сервер.

Сначала добавь импорт новой функции render_template из пакета Flask:

from flask import Flask, render_template
``

А затем добавь новый обработчик для нового запроса "/time":

```python
@app.route('/time')
def time_handler():
    return render_template("index.html")

Функция render_template возвращает текст файла, который ты укажешь. По умолчанию она ищет только среди файлов, находящихся в папке “templates”.

Попробуй перезапустить скрипт и зайти на: 127.0.0.1:1234/time

Ты увидишь страницу “index.html”, которую ты только что добавил.

Динамическая HTML страница

Похоже что на этой странице время остаётся одним и тем же – давай сделаем так, чтобы всегда выводилось текущее время.

Для этого надо зайти в наш шаблон “index.html”, и заменить статическое время на вот такое выражение:

<html>
 <body
  <h1>Current date and time: </h1>
 </body>
</html>

Что такое “”? Это значение шаблона, которое мы теперь можем подставлять из нашего Python скрипта.

Поменяй обработчик запроса “/time” таким образом:

# Добавь это в начало файла
import datetime

@app.route('/time')
def time_handler():
    cur_datetime_str = datetime.now().strftime("%d-%m-%Y %H:%M:%S")
    return render_template("index.html", cur_datetime=cur_datetime_str)

Что тут изменилось:

  • Добавился пакет datetime, который позволяет получать текущее время и дату
  • В параметры функции render_template добавился параметр cur_datetime – такой же как тот, который мы прописали в шаблоне

Попробуй перезапустить скрипт и проверь что изменилось. Обнови страницу несколько раз, и посмотри в какие моменты обновляется время.

Добавляем взаимодействие с пользователем

Давай добавим немного интерактива! Я модифицировал скрипт и шаблон таким образом, чтобы мы могли получать от пользователя его ник и сообщение.

На главной странице мы выводим ник и сообщение от последнего пользователя – получилось что-то отдалённо похожее на стену ВКонтакте.

Вот текст скрипта (комментарии описывают все новые моменты):

from flask import Flask, render_template, request
from datetime import datetime
import sqlite3

app = Flask('my_first_server')
DB_NAME = 'messages.db'

def init_wall_data():
    '''
    Открываем соединение с базой. Если файла базы ещё нет, то он создастся.
    Создаём таблицу с названием wall. Если она уже создана, то ничего не делаем.
    В этой таблице есть:
    - численный id, уникальный для каждой записи
    - строка nick - тут будет ник пользователя
    - строка message - тут будет сообщение, которое он оставил
    '''
    conn = sqlite3.connect(DB_NAME)
    conn.execute('''create table if not exists wall (
            id INTEGER PRIMARY KEY,
            nick TEXT,
            message TEXT)''')
    # Эта строчка сохраняет внесённые изменения -- она нужна при
    # создании таблиц и при добавлении/обновлении/удалении полей
    conn.commit()

def set_wall_data(nick, message):
    '''
    Записываем новое сообщение в таблицу wall
    '''
    conn = sqlite3.connect(DB_NAME)
    conn.execute('insert into wall(nick, message) values(?, ?)', (nick, message))
    conn.commit()

def get_wall_data():
    '''
    Забираем последнюю запись из таблицы wall
    '''
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.execute('select nick, message from wall')
    rows = cursor.fetchall()
    return rows[-1] if rows else (None, None)

def render_main_page():
    '''
    Отдельная функция, которая подставляет в шаблон все необходимые значения
    '''
    cur_datetime_str = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
    wall_data = get_wall_data()
    return render_template("index.html", cur_datetime=cur_datetime_str, nick=wall_data[0], message=wall_data[1])

@app.route('/wall', methods=['POST'])
def response():
    '''
    Обработчик, который принимает данные от пользователя,
    вставляет их в базу и возвращает обновлённую страничку
    '''
    nick = request.form.get("nick")
    message = request.form.get("message")
    set_wall_data(nick, message)
    return render_main_page()

@app.route('/')
def handle_time():
    return render_main_page()

init_wall_data()
app.run(port=1234)

Вот текст изменённого шаблона:


<html>
	<body>
	<h3>Current date and time: {{cur_datetime}}</h3>
	<br>
	<h3>Last message:</h3>
	{% if nick %}
		<b>{{nick}} says: </b>
	{% endif %}
	{% if message %} 
		<p>{{message}}</p>
	{% else %}
		<p>This wall is empty</p>
	{% endif %}
	<br>
	<b>New wall message:</b>
	<form method="POST" action="/wall">
		Nick: <input type="text" name="nick" required><br>
		Message: <input type="text" name="message" required><br>
		<input type="submit" value="Send">
	</form>
	</body>
</html>

Из нового в шаблоне конструкции “if” – они позволяют не выводить какие-то блоки, если переданное значение шаблона пустое.

Обнови скрипт и шаблон, и проверь как оно работает – теперь надо заходить на: 127.0.0.1:1234 (я убрал “/time”)

Разберись в том, что происходит. Если что-то не понятно из комментариев, то напиши мне – я объясню и дополню объяснение.

Если ты не знаком с SQL, который был здесь использован, то можешь обратиться к этой статье.

Выводим сразу все сообщения пользователей

Выводить только последнее сообщение как-то скучно – давай сделаем полноценную стену, чтобы было видно все сообщения.

В скрипте я изменил две функции:

def get_wall_data():
    '''
    Забираем все записи из таблицы wall
    '''
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.execute('select nick, message from wall')
    rows = cursor.fetchall()
    return rows

def render_main_page():
    '''
    Отдельная функция, которая подставляет в шаблон все необходимые значения
    '''
    cur_datetime_str = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
    wall_data = get_wall_data()
    return render_template("index.html", cur_datetime=cur_datetime_str,
            len=len(wall_data), wall_data=wall_data)

Теперь скрипт передаёт в шаблон список всех сообщений.

Так я изменил шаблон:


<html>
<body>
	<h3>Current date and time: {{cur_datetime}}</h3>
	<h3>Wall messages:</h3>
	{%for i in range(0, len)%}
		<b>{{wall_data[i][0]}} says: </b>
		<p>{{wall_data[i][1]}}</p>
	{%endfor%}
	<br>
	<b>New wall message:</b>
	<form method="POST" action="/wall">
		Nick: <input type="text" name="nick" required><br>
		Message: <input type="text" name="message" required><br>
		<input type="submit" value="Send">
	</form>
</body>
</html>

В шаблон добавлен цикл, который проходясь по всему списку, наполняет страницу сообщениями.

Все вот эти {%if%}, {%for%} и {{значения}} используются фреймворком Flask для генерации веб-страниц – в обычном HTML так делать нельзя.

Зайди на страницу, посмотри что изменилось.

Задание на закрепление

В получившемся веб-сайте добавь две функции:

  • Если пользователь пытается второй раз отправить ничем не отличающиеся данные – не вставляй их в базу
  • Отображай время, когда сообщение было оставлено:
    • Добавь в запрос создания таблицуы wall строковое поле “time” (удали файл базы данных, чтобы таблица пересоздалась)
    • При вставке новой записи в таблицу wall, записывай в “time” строкой текущее время (бери его так же, как мы выводим текущее время)
    • Модифицируй шаблон и функцию get_wall_data(), чтобы в сгенерированной странице для каждого сообщения выводилось время, когда оно было оставлено

Заключение

Итого, мы изучили:

  • Веб-сервер (сервер, выдающий HTML страницы)
  • Статические и динамические страницы (одинаковые для всех, создаются под каждого пользователя)
  • Фреймворк (расширяемая основа приложения)
  • Django и Flask (сложный и мощный, простой и лёгкий)
  • Создание веб-сервера
  • Функции-обработчики для разных запросов
  • Шаблоны HTML страниц
  • Подстановка значений в шаблон

Если что – пиши, я помогу и постараюсь объяснить лучше.