Салимóненко Дмитрий Александрович

Вычислительные Сети, Системы и Телекоммуникации

ЗАДАНИЕ 4: Создание простого парсера сайтов на языке С в ОС LINUX




Введение

В этом задании мы с Вами потренируемся взаимодействовать с сайтами из ОС Linux на основе:

  1. консоли (bash),
  2. соответствующей программы, разработанной Вами самостоятельно.

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

Вообще говоря, для (как минимум, временного) скачивания страниц (файлов) с того или иного сайта предназначена такая программа, как браузер. Их много: Mozilla Firefox, Internet Explorer, Safari, Opera, Google Chrom. Недавно добавился еще и Яндекс-браузер. Также стоит упомянуть отдельно – браузеры, раобтающие на мобильных платформах (планшетах, смартфонах и др.). Наконец, есть еще множество малоизвестных, но, в какой-то мере – тоже функциональных браузеров.

Что делает браузер для того, чтобы пользователь мог увидеть в его окне открываемую (загружаемую) им страницу? Если говорить очень обобщенно, то браузером осуществляется следующее:

  1. Посылка запроса к соответствующему серверу. Запрос включает в себя так называемые заголовки. В их состав входит, в частности, адрес в интернете, который необходимо открыть. Вот пример адреса: 4846d.ru.
  2. Получение ответа от сервера. Ответ включает в себя заголовки, отправляемые сервером, пустую строку (это важно) и html-код страницы, которую запросил браузер.
  3. Сохранение (временное) и обработка ответа сервера. Браузер, руководствуясь заголовками, полученными от сервера, определяется, каким именно образом он будет обрабатывать полученный html-код. В частности, речь идет об особенностях кодировки полученной вебстраницы (в виде html-кода), версии протокола (http 1.1 или т.п.), объем контента, полученного браузером, а также, быть может, некоторую другую информацию.

Итак, - три основных этапа открытия вебстраницы браузером. Мы с Вами рассмотрим лишь наиболее легкие – первые два этапа.

Что касается третьего – обработки html-кода в соответствии с «указаниями» сервера и, на этой основе, отрисовка вебстраницы в окне браузера – это представляет собой, в настоящее время, вовсе нетривиальную, трудоемкую задачу. Дело в том, что html-код состоит из html-тегов, которых в настоящее время имеется не один десяток – около сотни. У каждого из тегов могут быть атрибуты, свойства, количество которых также может быть немалым. Кроме того, необходимо еще и корректно обработать все вызовы javascript, имеющиеся на открытой (и кешированной браузером) странице.

Поэтому создание браузера в настоящее время – это задача достаточно сложная и длительная. Если, конечно, вести речь о программировании его функций – вручную, без использования готовых библиотечных компонентов, которые (например, в языке Delphy) позволяют создать простой браузер буквально путем написания нескольких строчек программного кода. Этим мы заниматься не будем. Банально, ибо.

Кстати, если создавать браузер, используя упомянутую Delphy, то мы получим... собственную, авторскую версию браузера... Internet Explorer. Да, именно так. И вовсе не "свой индивидуальный" браузер.

Что касается второго этапа – анализ заголовков, присланных в ответе сервера – то это тоже не настолько уж простая, как могло бы показаться, задача. Дело в том, что разные серверы могут ответить, в общем случае, по-разному – даже в ситуации, когда браузером присланы одни и те же заголовки. Кроме того, доступ к некоторым страницам может быть ограничен, некоторые страницы могут быть перемещены, некоторые – временно недоступны (о чем сервер, естественно, честно сообщает) и т.д. Все подобные ситуации браузер, естественно, должен корректно обработать. Этим мы с Вами также заниматься не будем, ибо оно, что называется, долго и нудно.

А еще - легко наделать ошибок и получить, как говорят программисты, "кривой" браузер.

А вот первый этап (отослать заголовки серверу) и получить от него ответ, выведя его на консоль или сохранив где-нибудь в файле – это проще и вполне реализуемо в рамках стандартного изучения дисциплин, типа «Вычислительные сети, системы и телекоммуникации», «Операционные системы» и др. Сохраненный файл html можно вполне открыть (но, уже, как Вы поняли, ЛОКАЛЬНО) после в каком-нибудь браузере – и получить в результате открытую вебстраницу.

Что же, приступим к делу

Попробуем «добыть» html-код интересующей нас вебстраницы БЕЗ ПОМОЩИ БРАУЗЕРА. Для этого, естественно, нам необходимо тем или иным образом скачать соответствующую страницы и сохранить ее где-нибудь у себя на компьютере. Т.е. в каком-нибудь файле.

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

Примечание. Отметим, что способ, о котором пойдет речь ниже, основан на HTTP запросах к интернет-серверу. Которые, в свою очередь, направляются ТСР-протоколом путем его, скажем так, стандартного использования. Т.е. программировать его работу, как это делалось в заданиях по сокетам, мы не будем.

Хотя, в принципе, вполне возможно переслать HTTP-заголовки серверу (находящемуся в интернете), используя собственный, авторский ТСР-сервер (находящийся на нашем компьютере). В одном из заданий по сокетам, а также при разработке сетевого анализатора трафика, мы с Вами использовали так называемые сырые сокеты (SOCK_RAW), в том числе и низкоуровневые, работающие не на транспортном, а на канальном уровне (PF_PACKET – пакетные сокеты).

Однако, если мы решим посылать соответствующие сообщения интернет-серверу HTTP (не путайте с серверами ТСР, которые фигурировали в заданиях по сокетам) с использование протоколов ТСР, а то и протоколов канального уровня, нам придется не менее, чем полностью моделировать работу протокола HTTP. А это, во-первых, весьма кропотливая работа. Как уже говорилось, может существовать множество ситуаций (ответов сервера), которые так или иначе надо будет обрабатывать.

Точнее, не «так или иначе», а в точном соответствии с требованиями соответствующих RFC.

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

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


Скачивание вебстраницы с сервера с использованием транспортного сокета

Итак, рассмотрим один из «почти низкоуровневых» способов скачивания вебстраницы. Соответствующая идеология описана здесь.

Для этого нам понадобится, как обычно, создать сокет и, затем, при его помощи скачать вебстраницу. Открыть TCP/UDP-сокет при помощи консоли, используя командную оболочку bash, можно, воспользовавшись следующей синтаксической конструкцией:

exec {дескриптор-файла}<>/dev/{протокол}/{адрес-узла}/{номер-порта}

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

Напомню, что ранее, когда мы с Вами программировали работу эхо-клиентов и эхо-серверов (работавших на базе протоколов ТСР/UDP), для создания сокета использовался программный системный вызов, вроде такого:

sock = socket(AF_INET, SOCK_STREAM, 0);

Сейчас же мы используем консольную команду.

Следует учесть, что файловые дескрипторы 0, 1 и 2 зарезервированы, соответственно, за стандартными потоками ввода (stdin), вывода (stdout) и ошибок (stderr). Поэтому в качестве файловых дескрипторов следует брать те значения, которые выше 2 и которые не используются, конечно.

Символы "<>" означают, что сокет должен быть открыт для чтения (“<”) и записи (“>”). Естественно, можно открыть сокет лишь для чтения или лишь для записи.

В поле "протокол" должно находиться наименование транспортного протокола ("tcp" или "udp").

Поле "адрес-узла" должно содержать имя открываемой вебстраницы, например, 4846d.ru/index.html или просто 4846d.ru.

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

Или, можно попытаться обратиться к серверу, указав другой порт: не 80, а, скажем, 800. В итоге, в консоли возникнет знакомое сообщение:

connection refused

Рассмотрим пример. Для открытия двунаправленного TCP-сокета (т.е. в который можно писать и из которого можно читать), соединенного с основным портом HTTP-сервера сайта 4846d.ru, с файловым дескриптором, большим 2 (например, равным 3) следует выполнить следующую консольную команду:

$ exec 3<>/dev/tcp/4846d.ru/80

Примечание. Знак доллара (“$”) означает, что команда запускается в пользовательском режиме, т.е. не от имени администратора (root). Иначе вместо $ фигурировало бы #.

Чтобы убедиться в этом, если вы работаете в Ubuntu, попробуйте ввести в консоли

sudo su

Система, естественно, запросит пароль.

После верного ввода пароля последний знак командной строки должен смениться с $ на #. С этого момента все вводимые команды будут выполняться с правами администратора.

Для выхода из сессии администратора и возвращения в пользовательский режим (в целях безопасности) используется команда exit.

Сокет, созданный приведенной выше командой, теперь можно использовать для связи с сайтом, с его конкретной страницей – и скачать ее к себе на компьютер. Для этого следует написать что-то вроде:

echo -e "GET / HTTP/1.1\r\nHost: 4846d.ru\r\nConnection: close\r\n\r\n" >&3

Команда echo выводит на экран или в файл то, что является ее аргументом.

Атрибут -e обеспечивает, вначале, выполнение того, что идет дальше в кавычках (а уж после этого результат будет выведен). Иными словами, этот атрибут представляет собой аналог команды eval в языках программирования типа javascripr, PHP и др.

А в кавычках идет самый обычный запрос GET. Иными словами, в кавычках идут следующие заголовки:

GET / HTTP/1.1
Host: 4846d.ru
Connection: close

Первый заголовок показывает URL-адрес открываемой страницы, а также версию HTTP протокола, желаемую для клиента (в данном случае клиентом, как Вы уже поняли, является не браузер, а консоль). Хотя, по идее, консоли-то все равно, на какой версии протокола будет осуществляться передача данных. Но, лучше указать версию, чтобы сервер не задумывался и не выдавал уточняющие сообщения.

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

Второй заголовок указывает имя хоста – т.е. сервер, на который направляется запрос.

Наконец, третий заголовок указывает серверу, что мы с ним далее открытое соединение держать не собираемся; нам нужно лишь ответ сервера (состоящий из заголовков ответа, пустой строки и html-кода скачиваемой страницы) – и все. Ну, а если бы собирались, тогда бы указали что-то вроде:

Connection: keep-alive

Обратите внимание на символы типа \r и \n. Первый из них означает «возврат каретки», второй – переход на следующую строку. Как Вы уже поняли, при их помощи заголовки отделяются друг от друга.

Если же сервер встретит эти символы, повторенные ДВАЖДЫ (т.е. так: \r\n\r\n), то поймет, что сообщение клиента окончено и можно формулировать ответ. Фактически, конструкция \r\n\r\n означает пропуск ОДНОЙ пустой строки.

Примечание. Подумайте, почему – одной пустой.

Таким образом, выполнившись (если без ошибок), указанная строчка обеспечит скачивание файла, содержащего вебстраницу (точнее, заголовки ответа сервера + пустая строка + html-код страницы) к нам на компьютер и сохранение всего этого в составе временных файлов. Причем, файловый дескриптор у этого файла будет равен 3.

А как скачать с сайта не главную страницу, а - какую-либо другую? Для этого в запросе GET вместо указания на корневой каталог (символ / ) следует указать URL-адрес этой страницы (вместо доменного имени следует указать символ корневого каталога /):

echo -e "GET /about-site.html HTTP/1.1\r\nHost: 4846d.ru\r\nReferer: http://4846d.ru/about-site.html/\r\nConnection: close\r\n\r\n" >&3
Заголовок Referer отправляем, опять же, для надежности: чтобы сервер считал, что мы, типа того, находимся в браузере на соответствующей вебстранице и посылаем запрос именно оттуда - так будет меньше проблем с ответами сервера.
Осталось только вывести содержимое временного файла на экран, что осуществляется командой cat <&3. При этом с сервера будет скачана страница 4846d.ru/about-site.html :

$ cat <&3

Вообще, про ввод-вывод (в том числе – про их перенаправление) при помощи консоли Linux можно прочитать, например, здесь.

Итак, все три команды вместе:

$ exec 3<>/dev/tcp/4846d.ru/80
$ echo -e "GET / HTTP/1.1\r\nHost: 4846d.ru\r\nConnection: close\r\n\r\n" >&3
$ cat <&3

Если соединение с сервером было установлено, соответствующая вебстраница доступна для скачивания, то в консоли мы увидим, вначале, заголовки, принятые от сервера, а потом (через пустую строку) – непосредственно код html.

Так что же (как говаривал Сократ), попробуем. Копируем поочередно команды – и вставляем их в строку консоли Linux. Или – вводим их вручную. И, нажимаем клавишу Enter.

Если с очередной командой «все хорошо» (т.е. если нет сообщений об ошибках а это будет, в частности, если доступен сайт 4846d.ru из операционной системы, в которой Вы запускаете команды – в данном случае идет речь об операционной системе Linux), то через короткое время после нажатия Enter в консоли будет вновь возникать пустая командная строка. Это касается первой и второй команд.

Тогда как после обработки третьей – в консоли появятся, как уже говорилось, вначале заголовки, которые вернул сервер, затем пустая строка и, наконец – код html страницы 4846d.ru.

Кстати, попробуйте и такой вариант, когда все три команды объединены вместе (т.е. в одну строку) при помощи разделителя |. При этом единая команда будет выглядеть примерно так:

$ exec 3<>/dev/tcp/4846d.ru/80 | echo -e "GET / HTTP/1.1\r\nHost: 4846d.ru\r\nConnection: close\r\n\r\n" >&3 | cat <&3

После запуска единой команды отдельные ее части будут выполняться по очереди. Так что же? Попробуем… и, скорее всего, появится просто пустая строка и ни кода html, ни заголовков сервера.

А, почему? Может, все дело в том, что здесь не работает указанный выше разделитель | ? Нет, как раз РАБОТАЕТ – потому и получается пустая строка. Подумайте, почему же тогда возникала проблема?...
Причина кроется в том, что сервер еще не успел дать ответ, а уже выполняется команда cat. Ну, а раз ответа сервере ЕЩЕ НЕТ, что же выведет эта команда? Естественно, ничего. Впрочем, проблема-то даже не с третьей по счету командой (cat), а со второй.

Записываем html-код страницы сайта в файл

Очевидно, вместо консоли можно направить вывод заголовков и html-кода в какой-нибудь файл, который затем, в свою очередь, может быть открыт браузером. Предварительно, файл может быть каким-либо образом обработан.

Например, Вы можете (точнее, не Вы лично, а программа) вырезать из этого файла ВСЕ то, что мешает его нормальному просмотру в браузере. Ну, это разного рода мелькалки, «интересная» реклама, неожиданно всплывающие формы обратной связи и т.д., и т.п. Вполне можно заменить фавиконку сайта (если она, скажем, раздражает своим видом), удалить весь javascript (чтобы он не мешал просмотру страницы, формируя разные «эффекты»), а также мешающие части контента. Также можно, по своему усмотрению, заменить цвет на странице везде или по выбору, размеры шрифтов; можно требуемым образом передвинуть блоки, ну, и т.д.

Примечание. Правда, следует иметь в виду, что, будучи сохраненным на компьютере, html-файл будет открываться в браузере ЛОКАЛЬНО, т.е. при помощи протокола file, а не http. В этом Вы можете убедиться, посмотрев в адресную (точнее, командную) строку браузера. Поэтому уже невозможно будет взаимодействовать из этой страницы с соответствующим сервером в интернете (т.е. с сервером, откуда она была получена), например путем посылания с нее AJAX-запросов на сервер: этому будет препятствовать политика безопасности браузеров – политика одного источника. В соответствии с которой, невозможно послать сообщение со страницы, открытой в браузере по одному протоколу (в данном случае – file), на страницу, доступную по другому протоколу - http(s). Ибо при отправке сообщений необходимо, чтобы запросы (браузера) и ответы (сервера) направлялись:

  1. с использованием одного и того же протокола,
  2. в рамках одного и того же домена,
  3. с использованием одного и того же порта.

При нарушении хотя бы одного условия отправка сообщений будет невозможна (впрочем, браузер Internet Explorer – и только он – позволяет менять порт, т.е. третье условие на него не распространяется). Кроме того, будут ограничены и некоторые другие действия.

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

Впрочем, все, как обычно, несколько проще. Для обхода политики одного источника нам и потребуется-то - всего лишь виртуальный сервер в том или ином обличьи. Это может быть либо готовая, кем-то сделанная, реализация (например, общеизвестный Denwer или что-то вроде Node.JS) или авторская.

Как записать html-код, получаемый от сервера, в какой-нибудь локальный файл, например, в f.html? Для этого следует перенаправить вывод команды cat из консоли в этот файл (чтобы эта команда записала все то, что пришлет нам сервер, не в консоль, а в этот файл). И, уже ПОСЛЕ этого, реализовать, как и ранее, вывод из файла с дескриптором под номером 3.

А куда будет идти вывод из файла с дескриптором под номером 3? Очевидно, туда, куда его реализует команда cat. Но, так как вывод последней переопределен на файл f.html (вместо стандартного вывода stdout, т.е. вместо консоли), соответственно, субъектом вывода и будет являться этот файл. Возможно, кому-то это покажется сложным с первого раза, тем не менее, это - правда.

Это будет выглядеть следующим образом:

cat > f.html <&3

Видим, что вначале вывод этой команды перенаправляется (символ ">") в файл f.html. Затем задается, ЧТО именно будет выводиться. В данном случае, как и ранее, выводиться будет содержимое того временного файла, который имеет дескриптор под номером 3. А это, как мы помним – тот файл, в который было записано содержимое соответствующей страницы, т.е. 4846d.ru.

Выполнив указанную выше последовательность из трех команд (с модифицированной командой cat), видим, что в текущем каталоге (т.е. в том, в котором работает консоль; узнать наименование каталога можно, посмотрев, что написано в командной строке ПЕРЕД символом $) появился файл f.html.

Посмотрим, что это за файл – f.html

Его можно открыть двумя путями:

  1. В браузере,
  2. В каком-нибудь простом текстовом редакторе, например, gedit (в Linux).
Открываем браузером

В первом случае мы получим не что иное, как вид html-страницы – в том виде, в каком ее отобразит браузер (Firefox, Ubuntu). Видим, однако, что страница несколько странная:

Главная страница сайта 4846d.ru без применения стилей из файла style.css

  1. Во-первых, она открылась не совсем так, как из сети интернет (в ином виде, оформление отчасти нарушено),
  2. Во-вторых, «почему-то» отсутствуют символы после «2015-» (в самом низу),
  3. В-третьих, еще и сверху видны некие «странные» надписи, выполненные черным шрифтом.
  4. В-четвертых, скорее всего, кириллица (русский текст) будет нечитаемым.

Попробуем разобраться

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

<link rel="stylesheet" type="text/css" media="all" href="style.css" />

Оно и понятно: ведь файла style.css, в самом деле, нет в каталоге, из которого открыт f.html. Браузер не может найти этот файл, потому и не применяет стили, прописанные в нем. А вот если бы был указан полный (точнее, абсолютный) путь к этому файлу, т.е. если бы было:

<link rel="stylesheet" type="text/css" media="all" href="http://4846d.ru/style.css" />

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

Кстати, это касается и всех других ресурсов (файлов), подключаемых к странице f.html.

Точнее, если файл f.html будет запускаться не в среде виртуального сервера по протоколу типа http, а локально (по протоколу file), то, если файл стилей будет находиться в том же каталоге, что и файл f.html, указанная строчка должна бы выглядет так:

<link rel="stylesheet" type="text/css" media="all" href="style.css" />

2. Отсутствующих символов, на самом деле, нет и не было в коде html. Вы можете в этом убедиться, просмотрев исходный код открытой страницы (в браузере). А откуда же они взялись на странице 4846d.ru, загруженной из интернета (т.е. непосредственно с сервера)? Из того же самого файла style.css. Подключены через псевдоэлемент (ну, мне так было удобнее).

Кстати, открыв эту страницу в браузере из сети интернет, попробуйте выделить и скопировать все те символы, которые идут после «2015-». Причина того, что сделать это - категорически невозможно, объяснена в предыдущем абзаце. Самое интересное, что, вроде бы, поисковые системы (по крайней мере, Google) также неспособны прочитать этот отрывок контента страницы.

Кстати, данные символы, как, впрочем, и содержимое других псевдоэлементов, НЕ будут присутствовать в DOM вебстраницы. Потому и название - ПСЕВДОэлемент. Соответственно, обратиться к псевдоэлементам при помощи javascript - командами, типа document.getElementById ('...') - не получится. Единственная возможность - корректировать файл стилей.

Так как браузер не может найти файл style.css, соответственно, он не знает, какое содержимое идет после «2015-».

3. «Странные» надписи, идущие в самом верху страницы - это заголовки протоколов, возвращенных сервером. И которые впоследствии утилита cat записала в файл f.html.

Если открыть страницу в браузере из интернета, то он (браузер) вырезает из нее все заголовки (после того, как считает их и, что называется, «примет к сведению»), выдавая пользователю для просмотра все то, что идет после пустой строки (см. выше) - в визуальном, отрендерированном виде. А утилита cat ведь не вырезала заголовки; она лишь честно передала (точнее, перенаправила) все то, что записалось во временный файл, имеющий дескриптор под номером 3, в файл f.html.

Естественно, если этот файл планируется просматривать в браузере, помимо вышеуказанного нюанса с путями к файлам стилей и иных ресурсов (подключаемых к странице), необходимо также вырезать из него заголовки. Т.е. следует вручную сделать часть той работы, которую делает браузер.

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

«исследовать элемент» -> «консоль» -> «сеть».

И вот что обнаруживаем:

Главная страница сайта 4846d.ru с GET-запросами и заголовками одного из запросов

В левой части видим ряд запросов GET. Вспомним, что в команде

$ echo -e "GET / HTTP/1.1\r\nHost: 4846d.ru\r\nConnection: close\r\n\r\n" >&3

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

Также видим, что браузер-то скачал, помимо самой страницы, еще и пару файлов с расширением .css (строки 2, 3 в запросах GET). К сожалению, строка 3 получилась закрытой всплывающим сообщением, но, поверьте на честное слово – что там как раз и фигурирует GET-запрос на скачивание файла style.css. А мы, скачивая страницу вручную, не делали таких запросов. Потому, и файлы стилей не скачивались.

Нажав левой кнопкой мыши, скажем, на первую строку запросов GET, видим заголовки соответствующего запроса. И еще мы видим, что запрос браузера, судя по заголовкам запроса, несколько более сложный, чем наш. Например, браузер указал, что он относится к модели Firefox, имеет версию 33.0. Также указал движок, на котором он выполнен (Gecko) и кое-какую другую информацию.

Мы ничего подобного не указывали, но, сервер все равно, как оказалось, выдал страницу.

Так что, если нам захочется смоделировать поведение того или иного браузера (т.е. «прикинуться» каким-либо другим браузером), это можно легко сделать, добавив, в консольной команде, соответствующие заголовки в запрос. Предпочтительно – именно в том виде и порядке, в котором они перечислены выше (на картинке). При этом вторая команда (из трех рассмотренных выше) имела бы примерно такой вид:

echo -e "GET / HTTP/1.1\r\nHost: 4846d.ru\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9b5) Gecko/2008050509 Firefox/3.0b5\r\nAccept-Language:ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Charset:windows-1251,utf-8;q=0.7,*;q=0.7\r\nContent-Type: application/x-www-form-urlencoded; charset=utf-8\r\nConnection: close\r\n\r\n" >&3

Но, следует учесть, что сервер может выдать несколько иную страницу – в зависимости от вида браузера. Точнее, в зависимости от информации, которую ему сообщит приложение (браузер, консоль или еще что), выполняющее запрос. Ибо все, что сервер знает о клиенте (например, о браузере), за исключением IP-адреса сетевого устройства, через которое производится запрос, содержится исключительно в заголовках, посылаемых клиентом. Собственно, для того-то и посылаются заголовки в запросе, чтобы сервер знал, с кем именно он имеет дело.

Некоторые серверы могут не выдать страницу, если заголовки указаны некорректно; т.е. если они не соответствуют ни одному из известных браузеров. Правда, такое поведение серверов – редкость в настоящее время. Современные серверы стараются, по возможности, выдать страницу даже в случае некорректных заголовков запроса. При этом некорректные ситуации обрабатываются так или иначе – чтобы, по возможности, соблюсти правила протокола HTTP(S). Равно как и правила протоколов ТСР, IP, впрочем.

Оттого, кстати, коды современных серверов – вещь достаточно сложная и объемная. Писать вручную их не рекомендуется, лучше использовать готовые, уже опробированные решения. О чем уже говорилось выше.

Открываем файл f.html текстовым редактором

Используя, например, gedit (или можно открыть этот файл в ОС Windows, скажем, при помощи Notepad++), видим примерно следующее. В самом начале идут те самые заголовки (полученные от сервера), потом – пустая строка и, наконец – сам html-код скачанной с сервера страницы и сохраненной в файле f.html.

Обратите внимание, что кириллические символы имеют нечитаемый вид. Причина тому – кодировка страницы html (содержащейся на сервере), не совпадающая с кодировкой редактора gedit (и кто ж его знает, как ее там настроить). Кстати, и в консоли будет такая же ситуация: кириллица будет нечитаемой.

Причина тому - несовпадение кодировок у меня на сайте (Windows-1251) и в консоли Linux (utf-8). В принципе, это - дело поправимое. Надо лишь установить для консоли кодировку, совпадающую с кодировкой скачанной страницы, т.е. Windows-1251. Вот как это делается в Ubunru:

Выбор и установка кодировки в консоли Linux Ubuntu

Кстати, у меня ее по умолчанию в списке не было, пришлось выбрать через Add or Remove.... Правда, там ассортимент кодировок настолько убогий (а ведь 21-й век-то уже на дворе, по идее), что. Ну, да ладно. Наверное, можно добавить (вручную, а как же еще) дополнительные кодировки, предварительно скачав их откуда-нибудь. Это - на совести разработчиков Linux. Которые, вместо того, чтобы довести до ума, отшлифовать хотя бы одну версию той или иной Linux, штампуют все "новые" и "новые" версии.

Если же открыть этот файл в OC Windows, то все выглядит вполне читаемо:

Исходный код главной страницы сайта 4846d.ru, открытый в текстовом редакторе Notepad++

Кстати, сервер не уточнил, в какой кодировке следует читать страницу; просто, мол, тип контента text/html, да и все. А дальше, типа того, сами думайте, что к чему. И если бы указание на кодировку не присутствовало в соответствующем метатеге, то браузер, скорее всего, открыл бы страницу в нечитаемом виде; и пришлось бы выбирать кодировку вручную, нажав в браузере

Вид -> Кодировка -> ...

Подобная ситуация случалась, иной раз, лет 5…10… назад на некоторых некачественно сделанных сайтах. Сейчас подобные сайты поисковые системы (Google, Яндекс и др.) стараются не пускать в первые позиции поисковой выдачи.

Возможно, подобная технология работы с серверами покажется несколько надуманной, искусственной. Конечно, удобнее и эффективнее (с большими возможностями) было бы - перехватывать сетевой трафик, анализировать получаемый html-код страницы сайта, делать там требуемые изменения и уже ПОСЛЕ этого отдавать браузеру.

Однако, это будет непростой задачей. Потребуется разработать так называемый сниффер, который будет функционировать на уровне драйвера, т.е. на уровне ядра ОС. Впрочем, возможно определенное упрощение такой задачи путем инсталляции в ОС виртуального сервера, например, Node.js и реализации сниффера уже на его базе. Мы с Вами этим заниматься не будем, ибо тот же Node.js – это отдельная область технологий, требующая изучения, пожалуй, в течение половины семестра – как минимум.

Кстати, не путайте виртуальный сервер Node.js и виртуальный сервер типа Denwer. Это – немного разные технологии.

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

Утилиты wget и curl

Основная функциональность этих утилит изложена здесь.

Утилита wget

Например, чтобы скачать файл file.html с сайта example.com, можно воспользоваться командой:

wget http://example.com/file.html

Можно также скачать файлы по шаблону имени, ограничить количестов попыток на скачивание, указать имя и параметры браузера (т.е. «прикинуться» браузером), реализовать рекурсивное скачивание (путем переходов по ссылкам), скачивать также все ресурсы, необходимые для отображения страницы, проверить ссылки в файле на доступность и т.д.

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

Утилита wget встроена в Linux (по крайней мере, в Ubuntu).

Утилита curl

Curl поддерживает протоколы не только http(s), но и DICT, FILE, FTP, FTPS, Gopher, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet и TFTP.

Скорее всего, утилиту curl придется устанавливать самостоятельно.

В отличие от wget, утилита curl позволяет реализовать, к примеру, самописный ftp-клиент. Она может выполнить POST-запрос, установить cookies, сохранить последние в файл, при скачивании страницы следовать по редиректам (при наличии).


Задание для самостоятельной разработки

Итак, у нас с Вами есть одна из технологий для работы с сервером. Правда, она реализована в консоли. Как реализовать работу программным образом? Для этого послужит одна из функций семества exec.

Примечание. В консоли функция ехес имеет иной функционал, по сравнению с тем, когда она запускается из программы на языке С/С++.

Следует разработать программу на С, которая будет выполнять:

1. Скачивание определенной страницы с сайта. Лучше, если указание адреса конкретной страницы будет реализовано в программе путем запроса соответствующего параметра через командную строку; или же можно задавать адрес страницы в строке параметров запускаемой программы через пробел (для чего в тексте программы следует использовать массив argv[]), например, в виде:

$ ./a.out 4846d.ru/about-site.html

При скачивании не забудьте, что не стоит пытаться выполнить все три команды почти одновременно (т.е. через очень короткие промежутки времени) – об этом уже говорилось выше. В учебных целях, для простоты, можно задать тайм-ауты между командами, равные, к примеру, 1…3 секундам.

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

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

2. Сохранение скачанной страницы в определенный файл, например, имеющий имя f.html. Естественно, не стоит располагать этот файл в системных каталогах.

3. Поиск и удаление обоих метатегов с фавиконками из html-кода скачанной страницы (т.е. из файла f.html); вместо каждого из этих метатегов должны появиться комментарии вида <!-- favicon deleted -->. Это удобнее и быстрее сделать, используя регулярные выражения (хотя, можно, для тренировки, использовать и свой, авторский алгоритм поиска и удаления). Последние позволяют, в частности, удалить из текста ту часть, которая соответствует определенному шаблону. Или - изменить ее требуемым образом. Причем, функционал регулярных выражений работает, как правило, быстрее, чем если писать аналогичный код собственной разработки. Хотя, именно в языках С/С++ разница в быстроте будет не столь заметна, по сравнению, к примеру, с языками типа javascript, PHP, Python.

4. Оформление совокупности заголовков (содержащихся в начале файла f.html) в абсолютно позиционированный блок, расположенный, при открытии этого файла браузером, в начале экрана, т.е. путем обертки его тегами

<div style="position: absolute">... Заголовки ...</div>

5. Оформить страницу в виде обрамления черной рамкой. Для этого нужно задать соответствующий стиль для тега <body>. Удобнее всего это сделать путем добавления соответствующего стиля в теги <style> ... </style>, расположенные в начале страницу, в разделе <head> ... </head>. В частности, для создания черной рамки тега <body> следует вставить в теги style строчку вида

border: solid 2px black;

Здесь, конечно, Вам пригодятся хотя бы минимальные знания языка html.

После этого следует открыть файл f.html браузером. Должна открыться страница, скачанная программой с http://4846d.ru/about-site.html с помещенным поверх нее блоком с заголовками. Блок должен иметь границу черного цвета (в виде рамки). Вся страница также должна быть обрамлена черной рамкой.


С уважением, Салимоненко Д.А.