undefined

Подготовка к практическому заданию

Harvard CS50
7 уровень , 5 лекция
Открыта

Цели

  • Ознакомиться с протоколом HTTP.
  • Применить привычные приемы в новом контексте.
  • Перейти от Си к веб-программированию.

Запустите «Виртуальную лабораторию CS50» или CS50 IDE, и в терминале выполните команду:

update50

чтобы обновить окружение до актуальной версии.

Как и в практическом задании пятой недели, задачник шестой недели идет вместе с кодом, который нужно загрузить перед началом работы. В консоли перейдите в папку Dropbox, если вы работаете в «Виртуальной лаборатории CS50» (workspace для CS50 IDE) или любую другую, которую вы используете для работы и выполните следующую команду:

wget http://cdn.cs50.net/2014/fall/psets/6/pset6/pset6.zip

чтобы загрузить ZIP-архив с материалами к заданию. Если вы теперь выполните команду ls, по идее, вы увидите в вашей рабочей директории файл pset6.zip.

Разархивируйте его командой:

unzip pset6.zip

Если снова выполнить ls, вы заметите появление папки pset6. Теперь мы можем удалить архив:

rm -f pset6.zip

Перейдите в папку pset6:

cd pset6

выполните:

tree

tree — это такой рекурсивный иерархический вариант ls. Вы также увидите, что содержит папка, только в несколько другом виде и с
подпапками:

Подготовка к практическому заданию - 1

Незадача, снова этот Си нарисовался! Но помимо «сишного» файла есть еще несколько интересных вещей. Так что продолжаем. Откройте в gedit (или CS50 IDE) файл cat.html. Он достаточно прост, не так ли? Похоже, что в нём есть тег img, значение src которого — это cat.jpg.

Далее, открываем hello.html в gedit (или в CS50 IDE). Обратите внимание, он содержит форму для передачи через GET текстового поля name к hello.php. Присмотритесь к коду в файле hello.php. Преимущественно здесь HTML-код, но в теле есть и часть PHP-кода:

<? = Htmlspecialchars ($ _ GET [ "name"])?>

Нотация &lt? = просит «передать следующее значение сюда».

А вот htmlspecialchars — всего лишь специфически названная функция. Её задача — заверить, что специальные символы (наподобие ^lt) воспроизведены правильным образом как сущности HTML. Если интересно знать больше, посмотрите здесь: php.net/manual/en/function.htmlspecialchars.php.

Далее, $ _GET — это суперглобальная переменная, в которую через GET поступают любые HTTP-параметры в hello.php. Точнее, это ассоциативный массив (хеш-таблица) с ключами и значениями. По форме в hello.html, одним из таких ключей должно быть name! Но самое главное в деталях.

Теперь перейдем к самой веселой части: откройте server.c в gedit (или CS50 IDE).
Да, вы угадали! Вы должны реализовать ваш собственный веб-сервер, который в курсе, как обрабатывать статический (файлы .html, .jpg, например) так и динамический (файлы .php) контент.

server.c

server.c

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

Ниже определены несколько констант, они определяют ограничения размера для HTTP-запросов. Мы же определили их на основе тех значений, используемых в популярном веб-сервере Apache (если вдруг интересно: http://httpd.apache.org/docs/2.2/mod/core.html).

Ниже определена константа OCTETS. Она указывает, сколько октетов будет считываться в буфер за один раз. Октет — сетевой термин для обозначения привычного байта (8 бит).

Следующими в группу подключены заголовочные файлы, и определён октет — восьмибитный тип, аналогичный char. Также присутствует несколько прототипов.

В конце, непосредственно перед функцией main, определенные глобальные переменные.
Обычно их лучше избегать, однако это не всегда возможно. Вскоре вы убедитесь, что здесь присутствует «обработчик сигналов», а он требует, чтобы определенные состояния сервера были глобальными, поэтому полное высвобождение памяти состоится при отключении сервера (Ctrl-C)

main

В самом верху тела main инициализирована глобальная переменная errno. По факту, она определена в errno.h и используется некоторыми функциями для указания, в случае необходимости, какая именно ошибка возникла. Подробности можно найти в документации по errno.

Ниже — вызов функции getopt, определенной в unistd.h. Она облегчает синтаксический разбор ввода из командной строки. Подробности — в документации по getopt (man 3 getopt).

Обратите внимание, каким образом мы используем getopt (и некоторые логические выражения), чтобы гарантировать, что сервер используется должным образом.

Следом идет вызов start (прототип которого вы могли заметить ранее). Подробнее об этом позже.

Далее видим:

signal (SIGINT, handler)

Это — указание «прослушивать» ввод на предмет SIGINT (Ctrl-C — команды отключения) и если это событие произойдёт, будет вызван handler (это ещё одна функция, определённая в другом месте).

Далее main входит в бесконечный цикл while. Внутри цикла вызывается reset — определенная нами функция, которая служит для высвобождения определенной памяти и перевод ряда параметров в нужное состояние между обработкой HTTP-запросов.

После этого есть вызов connected в операторе if. Независимо от того, вернёт connected true или false, она сделает это путем «блокирования», ожидая, пока браузер подключится к серверу перед возвращением значения.

Затем происходит вызов parse. Эта функция синтаксически анализирует HTML-запрос браузера и возвращает один длинную строку (char *) запроса и все заголовки, причем каждая строка отделена посредством CRLF (\ r \ n), вместо LF (\ n). Согласно спецификациям, строки в HTTP-сообщениях (таких, как RCF-запросы на комментарии) должны быть разделены с помощью CRLF (как и текстовые файлы в Windows). Подробнее можно посмотреть на https://tools.ietf.org/html/rfc7230. Обратите внимание, что функция parse записывает адрес обработанного запроса в глобальной переменной request, объявленной перед main.

Затем следует код, обрабатывающий этот запрос дальше, только непосредственно со строкой запроса. И тут есть небольшое задание для вас(TODO)! Подробности вы узнаете немного позднее.

Под TODO содержится написанный нами код для добавления поддержки .php файлов. На первый взгляд он очень сложен, но на самом деле, всё, что код делает на запрос, например, hello.php, это выполнение кода наподобие:

QUERY_STRING = "name = Alice" REDIRECT_STATUS = 200 SCRIPT_FILENAME = / home / jharvard / Dropbox / pset6 / public / hello.php php-cgi

Эффект от этого заключается в передаче содержания hello.php интерпретатору PHP (php-cgi) вместе с HTTP-параметром среды QUERY_STRING.

С помощью написанной нами функции load, мы записываем ответ интерпретатора в память. Адрес при этом сохранится в глобальной переменной body, также объявленной перед main. Теперь сервер посылает браузеру динамично сгенерированный ответ:

HTTP / 1.1 200 OK
Connection: close
Content-Length: 127
X-Powered-By: PHP / 5.5.9-1ubuntu4.4
Content-type: text / html

<! DOCTYPE html>

<html>
    <Head>
         <Title> hello </ title>
     </ head>
     <body>
        hello, Alice  </ body>
 </ html>

Возможно, использование popen, memmem, dprintf и write будет для вас в новинку. Прочитайте страницу документации (man). И знайте, что popen открывает «трубу» (pipe) для процесса (у нас для php-cgi), которую предоставляет указатель на файл (через него мы можем считывать стандартный вывод процесса так, как будто это был бы реальный файл).

Обратите внимание: функции dprintf, write, open и read используют дескриптор файла (значения int, вместо указателя FILE*) для обращения к файлам. Это важное, но не единственное отличие их от fprintf, fwrite, fopen и fread. Просто почитайте документацию для уточнения, что и где использовать.

Под этим кодом для работы с PHP-файлами есть ещё одна задачка для вас. Об этом тоже чуть позже.

Так вот, на этом main заканчивается! Обратите внимание, порой здесь используется continue для того, чтобы вернуться к началу бесконечного цикла, где вызывается reset. Иногда перед continue вызывается error с статус-кодом HTTP. Эти строки, вместе с теми, которые вы еще напишете, позволяют серверу обрабатывать и отвечать на ошибки прежде, чем обрабатывать новые запросы.

connected

Пробегитесь глазами по телу функции connected (под main). Не огорчайтесь, если непонятно, что она делает, но попробуйте догадаться, почитав страницы документации для memset и accept!

error

Уделите больше времени изучению error. Именно эта функция отправляет браузерам сообщения об ошибках (404, например). Она несколько длиннее, но содержит больше знакомых конструкций. Перед дальнейшей работой хорошенько разберитесь в ее работе.

load

Это функция, записывает файл в память, используя глобальную переменную file, определенную перед main. Просмотрите её код неспешно, постарайтесь понять, каким образом она использует один буфер buffer для считывания постоянного количества октетов за раз и соединяет их (в другом буфере (чей адрес содержится в body, другой глобальной переменной, находящейся перед main), размер которого по надобности может меняться (с помощью realloc).

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

handler

Как хорошо, что функция такая короткая! По сути, она только вызывает stop. А вызывается с помощью Ctrl-C

lookup

А тут еще одна задача для вас!

parse

Еще одна большая, очень похожа на load, функция. Parse считывает HTTP-запрос от соединения, отклоняет один CRLF и тело запроса (если таковой имеется), и оставляет только строку запроса и заголовки, адреса которых хранятся в глобальной request, также объявленной перед main. Заметьте, эта функция тоже в определенных случаях вызывает error.

reset

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

start

Это функция запускает весь процесс. Не беспокойтесь, если не понимаете (даже с документацией) все, что в ней написано. Там вы найдёте преимущественно код для сети. Просто знайте, что эта функция настраивает сервер, чтобы тот устанавливал соединение на нужный TCP-порт!

stop

А эта функция совершает ровно противоположные действия: она высвобождает всю память и полностью выключает сервер, даже без возвращения контроля в main.

Комментарии (6)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Artem 0 уровень
8 марта 2021
здравствуйте, коллеги. на следующей странице (здесь https://javarush.ru/quests/lectures/questharvardcs50.level07.lecture06 ) в комментах постарался подробно описать, как я боролся с этим заданием. надеюсь, кому-нибудь это может быть полезным.
Татьяна 1 уровень, Казань
28 июня 2019
Здесь больше объяснений к заданию (оригинал): https://cdn.cs50.net/2016/x/psets/6/pset6/pset6.html
Ilya Mikhailov 0 уровень, Киев
28 марта 2019
http://cs50.tv/2015/fall/#about,psets Все задачники в полном виде, но на английском
enot_00 1 уровень, Минск
24 декабря 2018
Подготовка к выполнению практического задания изложена ооочень халтурно. Чтобы увидеть как должен работать ваш сервер после загрузки, запустите в коммандной строке команду ~cs50/pset6/server public. Должно появиться сообщение

Using /home/ubuntu/workspace/pset6/public for server's root
Listening on port 8080
Где показан используемый путь к корневой папке сервера, и "слушаемый" порт (его и при реализации своей программы лучше оставлять таким. После запуска сервера в правом верхнем углу (при использовании IDE), жмете на кнопку SHARE, там будут указанны ссылки на вашу директорию. Во второй строке (Application) либо копируете ссылку открываете ее в новой вкладке, либо жмете на нее и на пункт OPEN. Если сервер запущен, то в новой вкладке вы увидите простенький список содержимого папки Public, ссылки кликабельны, и должны открывать и обробатываться должным образом (т.е. картинка открывается как картинка, PHP-файл открываться как PHP-файл). Так должен выглядеть результат решения вашей задачи. Теперь можно попробовать запустить свой сервер, который вы получили в папке PSET6, формат запуска немного иной. Для начала выполните компиляцию make, у меня в файле server была одна ошибка. Я пока задачу не дорешал, ошибку исправил, но сам сервер запускается. Потом в командной строке напишите следующее:

./server -p 8080  ~/workspace/pset6/public
(можно оставлять просто параметр -p, порт по умолчанию будет 8080) И если обновить соседнюю вкладку, в которой вы запустили "браузер", то вы скорее всего увидите ошибку:

501 Not Implemented
В командной строке сервера вы увидите: GET / HTTP/1.1 (запрос) HTTP/1.1 501 Not Implemented (ответ) Собственно, вам нужно допилить сервер таким образом, чтобы сервер "понимал" запрос "браузера" и адекватно на него отвечал.
Michaelk 2 уровень
28 января 2018
Насколько я понял, команда для скачивания актуального Pset6 изменилась Теперь вместо wget http://cdn.cs50.net/2014/fall/psets/6/pset6/pset6.zip Необходимо набирать wget http://cdn.cs50.net/2015/fall/psets/6/pset6/pset6.zip Старая версия немного отличается от новой и в ней отсутсвует несколько задач. Например нет задания - реализовать функцию indexes