Module 2.1

Веб-серверы и веб-приложения

Веб-серверы по сути являются программами, которые прослушивают входящие соединения (обычно на порту 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.

Loading

Запросы и ответы

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

# 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)

В этом курсе мы в основном будем использовать именно этот подход.

Loading

Представления для пользователей

Приложения, с которыми мы работали до сих пор, получали запрос к конкретному пути и отвечали строкой. Хотя это ровно та же сквозная функциональность, которую используют приложения, отправляющие пользователям 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>
Loading

Добавление данных в представление

Цель использования шаблонов — возможность динамически генерировать содержимое. Сначала нам нужно передать данные в шаблон. Самый простой способ сделать это — передать параметр контекста с помощью вспомогательной функции 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>
Loading

В предыдущем упражнении серверу нужно было отслеживать текущий список. Это делается с помощью sessions — функциональности, предоставляемой Django. По сути, Django позволяет поддерживать состояние для пользователя. Состояние — это объект, похожий на словарь, к которому можно обращаться через request.session. Поскольку веб-сервер обычно обслуживает несколько пользователей одновременно, сессии нужно идентифицировать для разных пользователей. Это делается через cookie: id выдается браузеру сервером, id сохраняется браузером в cookie и отправляется на сервер каждый раз, когда браузер делает запрос. Сессии по умолчанию хранятся в базе данных, то есть после перезапуска сервера сессия останется сохраненной.

Loading

В этой части курса по защите программного обеспечения мы сделали первые шаги к пониманию веб-приложений. В следующей части мы рассмотрим использование баз данных и базовый HTTP-протокол.

Вы дошли до конца этого раздела!

Не забудьте проверить свои баллы в индикаторе в правом нижнем углу материала!

В этой части:
  1. 1. Порты и приложения

  2. 2. Веб-серверы и веб-приложения