Веб-серверы и веб-приложения
Веб-серверы по сути являются программами, которые прослушивают входящие соединения (обычно на порту 80) и следуют протоколу HTTP. Изначально веб-серверы в основном использовались для отдачи статического содержимого, например HTML-страниц и забавных GIF-изображений, но сегодня они отдают динамическое содержимое, которое часто формируется отдельно для каждого пользователя.
Веб-приложения работают на веб-серверах. Веб-серверы прослушивают входящие соединения и п ередают их веб-приложениям. Разработчики, работающие с веб-приложениями, очень редко реализуют функциональность внутри самих веб-серверов.
Веб-приложения включают как клиентскую, так и серверную функциональность. Клиентская функциональность выполняется в браузере пользователя, то есть у вас, а серверная функциональность выполняется на веб-сервере.
Когда пользователь выполняет действие, например вводит URL и нажимает Enter или нажимает ссылку в браузере, компьютер пользователя отправляет запрос на сервер. Получив запрос, сервер обрабатывает его и формирует соответствующий ответ. Ответом может быть, например, HTML-код, JSON-данные или изображение, которое браузер должен показать пользователю.
При создании функциональности, отображаемой в браузере, разработка обычно сосредоточена на трех отдельных и взаимосвязанных направлениях. Структура представления создается с помощью HTML, макет и оформление — с помощью CSS, а возможная динамическая функциональность — с помощью JavaScript.
С другой стороны, при создании функциональности, выполняемой на бэкенде, то есть на сервере, разработчик обычно сосредотачивается на функциональности, необходимой для получения и формирования ответа, который нужно отправить пользователю. Это часто включает подключение к другому программному обеспечению, например к серверу базы данных, и получение данных оттуда. Сервер может, опять же, работать локально или на отдельной машине. Естественно, клиентская и серверная разработка могут быть связаны, и разработчики программного обеспечения обычно работают над задачами, относящимися к обеим сторонам. То есть в наши дни довольно редко разработчик занимается только, например, поддержкой базы данных.
Когда пользователь открывает онлайн-страницу в браузере, на сервер отправляется запрос. Сервер создает содержимое ответа и отправляет его обратно пользователю. Если содержимое содержит ссылки на другие ресурсы, например изображения или скрипты, каждый из этих ресурсов загружается браузером отдельно (за исключением модели HTTP/2 Server Push).
Создание простого веб-приложения
Основная функциональность веб-приложения — создавать ответ на каждый запрос. Разработчики обычно не реализуют функциональность веб-сервера и детали HTTP-протокола, а используют фреймворк, который абстрагирует многие существующие задачи. Здесь мы рассмотрим один такой фреймворк, Django. Для энтузиастов Java(TM) де-факто веб-фреймворком является Spring.
Рассмотрим упражнение "Привет, веб!" в TMC (само задание приведено ниже). Упражнение содержит минимальный веб-сервер, который можно запустить с помощью
python3 manage.py runserverи который должен быть доступен по адресу http://localhost:8000/, если порт
свободен.
В проекте много файлов; большинство из них — автоматически сгенерированные каркасные файлы, которые предоставляют функции, пока не важные для нас.
Нас интересуют два файла.
Первый файл — src/pages/views.py,
from django.http import HttpResponse
def homePageView(request):
return HttpResponse('Hello World!')а второй файл — src/pages/urls.py
from django.urls import path
from .views import homePageView
urlpatterns = [
path('', homePageView, name='home')
]Прежде чем продолжить, несколько слов о соглашениях по путям. Часть src в
пути нужна для работы TMC и обычно отсутствовала бы в типичном проекте Django.
pages — это имя нашего приложения. Приложения в Django — это способ группировать
сервисы, предоставляемые сервером. Имя pages могло бы быть другим, и один
веб-сервер может одновременно иметь разные приложения.
Файл urls.py сообщает серверу, что если клиент (браузер) запрашивает
корневую веб-страницу, в ответ должен быть предоставлен вывод
homePageView. Необязательный параметр name иногда удобен, если нужно
сослаться на путь из кода. Файл views.py содержит фактическое определение
homePageView.
Запросы и ответы
Веб-приложения обычно отвечают на запросы к нескольким путям, где у каждого пути есть конкретная функциональность. В примере ниже есть три отдельных пути, каждый из которых возвращает пользователю разную строку в качестве ответа.
# urls.py
from django.urls import path
from .views import pathView, trailView, routeView
urlpatterns = [
path('path/', pathView, name='path'),
path('trail/', trailView, name='trail'),
path('route/', routeView, name='route')
]# views.py
from django.http import HttpResponse
def pathView(request):
return HttpResponse('Path')
def trailView(request):
return HttpResponse('Trail')
def routeView(request):
return HttpResponse('Route')Каждый запрос может содержать информацию, отправляемую веб-приложению. В принципе, есть два способа обработать это: (1) добавить параметры в адрес или (2) добавить параметры в тело запроса. Тело запроса мы рассмотрим позже в курсе.
Есть два способа передавать аргументы в адресе. Первый способ — добавить
параметры непосредственно в путь. Например, путь
http://localhost:8000/greet/ada/ может быть разобран так, что ada станет
параметром представления, которое об рабатывает запрос. Разбор этого параметра
выполняется в urls.py.
# urls.py
from django.urls import path
from .views import greetView
urlpatterns = [
path('greet/<str:user>/', greetView, name='greet')
]# views.py
from django.http import HttpResponse
def greetView(request, user):
return HttpResponse('Hi ' + user)Другой, более распространенный способ — использовать параметры GET. В этом
случае пу ть выглядит как http://localhost:8000/greet/?user=ada. Тогда
соответствующий код будет таким:
# urls.py
from django.urls import path
from .views import greetView
urlpatterns = [
path('greet/', greetView, name='greet')
]# views.py
from django.http import HttpResponse
def greetView(request):
user = request.GET.get('user')
return HttpResponse('Hi ' + user)В этом курсе мы в основном будем использовать именно этот подход.
Представления для пользователей
Приложения, с которыми мы работали до сих пор, получали запрос к конкретному пути и отвечали строкой. Хотя это ровно та же сквозная функциональность, которую используют приложения, отправляющие пользователям HTML-содержимое, HTML-содержимое обычно создается с помощью шаблонов, содержащих встроенные команды для определения содержимого, которое должно быть добавлено в эти шаблоны. Здесь мы используем собственный язык шаблонов Django. Для поклонников Java(TM) правильным выбором является Thymeleaf.
Шаблоны для приложения pages должны быть помещены в папку
src/pages/templates/pages.
В примере ниже мы создали приложение, которое прослушивает корневой путь /.
Когда пользователь отправляет запрос к приложению, ему возвращается
HTML-страница, созданная на основе шаблона. Шаблон, используемый для создания
сайта, определяется строкой, которую возвращает метод — здесь
"pages/index.html". Это приведет к тому, что фреймворк будет искать шаблон с
именем index.html в src/pages/templates/pages/. Если страница найдена
(убедитесь, что имя правильное!), шаблонизатор Django обработает страницу и
вернет ее пользователю.
# urls.py
from django.urls import path
from .views import homePageView, videoPageView
urlpatterns = [
path('', homePageView, name='home')
]# views.py
from django.http import HttpResponse
from django.template import loader
def homePageView(request):
template = loader.get_template('pages/index.html')
return HttpResponse(template.render())<!-- index.html -->
<html>
<head>
<title>Hi</title>
</head>
<body>
Hello from the template side.
</body>
</html>Добавление данных в представление
Цель использования шаблонов — возможность динамически генерировать содержимое. Сначала нам нужно передать данные в шаблон. Самый простой способ сделать это — передать параметр контекста с помощью вспомогательной функции render.
# views.py
from django.shortcuts import render
def homePageView(request):
return render(request, 'pages/index.html', {'msg' : 'Hi!', 'from' : 'Ada'})Переменная контекста — это словарь, и он также может содержать вложенные словари и списки.
Затем контекст можно отрисовать в шаблоне с помощью синтаксиса {{}}.
<!-- index.html -->
<html>
<head>
<title>Hi</title>
</head>
<body>
{{msg}} from {{from}}
</body>
</html>Формы: содержимое из форм
Веб-приложения могут содержать формы, которые используются для отправки
содержимого в приложение. Формы определяются в HTML (см.
form) с помощью элемента
form. Элемент form содержит путь, на который будет отправлено содержимое, тип
запроса и данные. Пока типом запроса будет POST. Мы обсудим POST и GET позже.
Данные определяются с помощью полей, например поля ввода (<input
type="text"...), а содержимое отправляется на сервер с помощью кнопки
(<input type="submit"...). Форма ниже отправляется в корневой путь
приложения, а поле, в которое пользователь может вводить содержимое, имеет имя
"content".
<form action="/" method="POST">
{% csrf_token %}
<input type="text" name="content"/>
<input type="submit"/>
</form>Обратите внимание, что приведенный выше код содержит специальный тег Django
{% csrf_token %}. Этот тег сгенерирует скрытый input со случайным значением,
которое сервер запоминает. При каждой отправке формы скрытый input должен
совпадать с тем, что запомнил сервер. Это мера безопасности против CSRF-атак,
которые мы подробно обсудим позже.
Работа со списками
В шаблон также можно включать списки.
# views.py
from django.shortcuts import render
def homePageView(request):
return render(request, 'pages/index.html', {'msg' : 'Hi!', 'senders' : ['Ada', 'Alice', 'Bob']})Список можно перебрать с помощью синтаксиса {% for %} в шаблоне.
<!-- index.html -->
<html>
<head>
<title>Hi</title>
</head>
<body>
{{msg}} from
{% for user in senders %}
{{user}}
{% endfor %}
</body>
</html>В предыдущем упражнении серверу нужно было отслеживать текущий список. Это
делается с помощью sessions — функциональности, предоставляемой Django. По
сути, Django позволяет поддерживать состояние для пользователя. Состояние — это
объект, похожий на словарь, к которому можно обращаться через
request.session. Поскольку веб-сервер обычно обслуживает несколько
пользователей одновременно, сессии нужно идентифицировать для разных
пользователей. Это делается через cookie: id выдается браузеру сервером, id
сохраняется браузером в cookie и отправляется на сервер каждый раз, когда
браузер делает запрос. Сессии по умолчанию хранятся в базе данных, то есть
после перезапуска сервера сессия останется сохраненной.
В этой части курса по защите программного обеспечения мы сделали первые шаги к пониманию веб-приложений. В следующей части мы рассмотрим использование баз данных и базовый HTTP-протокол.
Не забудьте проверить свои баллы в индикаторе в правом нижнем углу материала!