Ruby/Сети: различия между версиями

Содержимое удалено Содержимое добавлено
Нет описания правки
орфография, викификатор
Строка 2:
 
=== Как написать [[w:Троянская программа|троян]]? ===
 
Однажды один из студентов попросил меня рассказать о том, как создать простейшее клиент-серверное приложение на Ruby.
 
Строка 8 ⟶ 7 :
 
==== Построение серверной части ====
 
На руках у него уже была серверная часть программы, которая позволяла манипулировать удалённой файловой системой:
 
Строка 48 ⟶ 46 :
end</source>
 
Программка, конечно, хорошая, но сдаётся мне, что она не работает. Первое, на что следует обратить внимание,  — это закрытие соединения после выполнения каждой команды. При работе с этим сервером через [[w:Telnet|telnet]] будут возникать определённые проблемы. Давайте предельно упростим специализированный код этой программы и посмотрим на структуру клиент-серверного приложения.
 
<source lang="ruby">require 'socket'
Строка 100 ⟶ 98 :
* <code>cmd, arg = session.gets.chomp.split</code>
 
Данный код интересен тем, что в программе студента для его реализации задействовано аж три строчки. И всё потому, что он не знал о деталях присваивания при работе с массивами. В данном случае переменная <code>cmd</code> получает значение нулевого элемента массива, переменная <code>arg</code> — - соответсвенносоответственно первого элемента массива. Сам же код получает от клиента строку, которая интерпретируется им как «команда и аргумент, разделенные пробелом». Далее эта строка обрезанная от служебных символов (<code>chomp</code>) преобразуется в массив (<code>split</code>).
Обратите внимание, что работа идёт с переменной <code>session</code>, а не <code>server</code>.
 
Строка 111 ⟶ 109 :
Для того, чтобы прекратить работу сервера необходимо нажать комбинацию клавиш <code>Ctrl+C</code> или <code>Ctrl+Break</code>. Команду отключения данный сервер не поддерживает. Команда <code>shutdown</code> относится к клиенту.
 
Несмотря на то, что данный сервер вполне рабочий, у него есть один существенный недостаток  — он не работает для нескольких клиентов. Для того, чтобы это реализовать необходимо обрабатывать каждое соединение с клиентом в отдельном потоке. Кто изучал мультипроцессорное программирование, тот понимает о чём речь. Но не стоит сразу кидаться в книжный магазин за необходимой литературой. В составе дистрибутива Ruby уже есть замечательная библиотека <code>gserver</code>, которая как раз и занимается тем, что реализует обработку запросов клиентов в отдельных потоках. Для демонстрации её работы перепишем предыдущую программу под <code>gserver</code>.
 
<source lang="ruby">require 'gserver'
Строка 160 ⟶ 158 :
* <code>def serve(session)</code>
 
Метод <code>serve</code> ({{англ|serve}}  — обслуживать) используется классом <code>GServer</code> как обработчик сессии с клиентом. При выходе из метода сессия автоматически закрывается. Обратите внимание, что переменная <code>session</code> является параметром метода. Весь код обработки сессии взят из предыдущей программы без изменений.
 
* <code>troyan = Troyan.new(31337)</code>
Строка 183 ⟶ 181 :
 
==== Построение клиентской части ====
 
Как мы уже видели выше, серверная часть полностью определяет функциональность всего клиент-серверного приложения. Клиентская же часть лишь принимает от пользователя запросы, пересылает их серверу и получает ответ, который передаёт пользователю.
 
Строка 254 ⟶ 251 :
troyan.join</source>
 
Была добавлена лишь одна команда (хотя самые внимательные могут заметить, что ещё и метод <code>.print</code> был заменён на <code>.puts</code>)  — <code>session.puts "+OK"</code>. Она будет выполнятся после каждой передачи данных от сервера к клиенту. Тем самым мы будем извещать клиента о том, что передача завершается. Теперь перепишем клиент. Необходимо исправить код там, где происходит чтение, чтобы он учитывал строки <code>+OK</code>.
 
<source lang="ruby">require 'socket'
Строка 400 ⟶ 397 :
 
=== Как создать сетевой блокнот? ===
 
Идея написать подобную программу появилась после прочтения статьи [http://doci.nnm.ru/php_forall_/22.11.2006/sozdaem_svoj_onlajn_bloknot/ Создаём свой online-блокнот]. Продемонстрированная там программа предельно проста, но на её примере очень удобно показать работу с [[w:ERB|ERB]]. А учитывая тот факт, что ERB используется в составе инструментария [[w:Ruby on Rails|Ruby on Rails]], то ценность этой статьи становится очевидной.
 
==== Первое приближение ====
 
В первом приближении мы попытаемся реализовать ту же самую функциональность, что и описана в статье. Вот только [[w:PHP|PHP]] подразумевает наличие веб-сервера, который будет заниматься интерпретацией его команд. В нашем же примере мы самостоятельно поднимем веб-сервер (написанный на Ruby), чтобы не заморачиваться с настройкой стороннего.
 
Строка 469 ⟶ 464 :
 
==== Добавляем ERB ====
 
Теперь приступим к ERB. Это шаблонная библиотека, которая позволяет осуществлять вставку в произвольный текст любого Ruby-кода. Для этого имеются два основных тега:
 
Строка 519 ⟶ 513 :
 
==== Выносим ERB-шаблон во внешний файл ====
 
ERB-шаблон существенно портит красоту нашего Ruby-кода. Поэтому, решение вынести шаблон во внешний файл вполне оправдано. Тем более, что это позволит нам править ERB-шаблон отдельно от программы. Более того, внесенные в шаблон изменения будут вступать в силу без перезагрузки сервера.
 
Строка 550 ⟶ 543 :
 
==== ERB-шаблон превращается в ERB-сервлет ====
 
Если ERB-шаблон подключать, как ERB-сервлет, то программа существенно упрощается за счет того, что логику, которая отвечает за формирование данных можно вынести в шаблон. Чтобы это ощутить, достаточно взглянуть на новую версию нашей программы:
 
Строка 579 ⟶ 571 :
 
=== Гнёзда, которые свили не птицы ===
 
[[Файл:Гнезда.png|frame|center|Примерное содержание главы]]
 
=== Как пропинговать компьютер в сети? ===
 
Открываем новую серию статей, которые будут рассказывать про использование встроенных библиотек Ruby. Первая статья будет посвящена написанию утилиты <code>ping</code> (в очень упрощённой форме). Смотрим в стандартную библиотеку и обнаруживаем файлик <code>ping.rb</code>. Смотрим в него и обнаруживаем метод <code>pingecho</code>. Метод используется следующим образом:
 
Строка 610 ⟶ 600 :
 
=== Простейший датчик работы службы ===
 
Летним воскресным утром мне захотелось сделать что-то приятное и красивое… Я написал письмо в конференцию о моих светлых идеях, но не смог его отправить. [[w:SMTP|SMTP]]-сервер безбожно висел. И решило моё больное воображение написать программку, которая документировала бы подобные ситуации. В общем мне нужен был простейший документ (лог работы службы), который бы доказывал, что наши сетевые админы зря едят свой хлеб (а на самом деле я добрый). Для начала я определился с информацией, которая мне была нужна. А именно:
 
Строка 654 ⟶ 643 :
}</source>
 
Великолепно! Программа работает… но иногда зависает. И тогда меня посетила ещё одна мысль: на соединение отводить всего одну секунду (не уложился  — сам дурак). Если соединение зависало, то в файл записывалось <code>timeout</code>. Чтобы «отводить время на выполнение операции» нужно задействовать библиотеку <code>timeout</code>. Она у меня входила в состав дистрибутива. Итак, переписываем нашу программу:
 
<source lang="ruby">require 'timeout'
Строка 704 ⟶ 693 :
<code>user system total (real)</code>
 
Нам нужен только <code>real</code>. Всё остальное  — для более детального анализа. Поэтому немного доработаем результат замыкания <code>measure</code>:
 
<source lang="ruby">beachmark = Benchmark.measure{
Строка 745 ⟶ 734 :
 
=== Датчик обновления сайта ===
Ни для кого не секрет, что [[w:Сетевой администратор|админы]]  — это жутко ленивый народ. Но начальство, как назло, не хочет платить деньги за просто так. Ему нужны отчёты! Представляю на ваш суд датчик обновления новостной ленты сайта. Для начала нужно как следует поставить себе задачу:
 
Ни для кого не секрет, что [[w:Сетевой администратор|админы]] — это жутко ленивый народ. Но начальство, как назло, не хочет платить деньги за просто так. Ему нужны отчёты! Представляю на ваш суд датчик обновления новостной ленты сайта. Для начала нужно как следует поставить себе задачу:
 
* Есть сайт, и админ его частенько обновляет.
Строка 754 ⟶ 742 :
* Новости располагаются на первой странице сайта.
 
Итак, для чего нам нужен датчик? Допустим вы  — админ сайта и постоянно добавляете новости. По окончании недели (месяца, года, столетия, …) от вас требуют отчёт о проделанной работе. Вам приходится заходить на сайт и смотреть те новости, которые вы добавили за последний период. Муторно и неэффективно. Намного приятнее постоянно вести записи о добавленных новостях (при помощи программы, конечно) и по завершении периода просто сделать соответствующую выборку.
 
Итак, немного об алгоритме программы. Обычно новостей на главной странице строго определённое количество. На нашем тестовом сайте их ровно пять. У нас есть файл, в котором мы храним дату добавления новости и заголовок новости. Разделитель у нас может быть произвольным, но в качестве примера будет использован набор символов <code>' ^_^ '</code>. Вообще для данной задачи даже разделитель не очень-то и нужен (дата состоит из строго определённого количества символов и записывается в строго определённом формате), но универсальность превыше всего!
Строка 779 ⟶ 767 :
 
=== Как скачать HTML-страницу ===
Для скачивания HTML-страниц обычно используется протокол HTTP. Адрес страницы задаётся в виде URL. В Ruby существует несколько способов скачать страницу по протоколу HTTP. Начнём с самого древнего — класса <code>Net::HTTP</code> (древнее только Socket’ы, но про них мы умолчим). Для определённости мы будем скачивать этот учебник и сохранять его в виде файла <tt>RubyBook.html</tt>.
 
Для скачивания HTML-страниц обычно используется протокол HTTP. Адрес страницы задаётся в виде URL. В Ruby существует несколько способов скачать страницу по протоколу HTTP. Начнём с самого древнего – класса <code>Net::HTTP</code> (древнее только Socket’ы, но про них мы умолчим). Для определённости мы будем скачивать этот учебник и сохранять его в виде файла <tt>RubyBook.html</tt>.
 
<source lang="ruby">require 'net/http'
Строка 801 ⟶ 788 :
 
==== Запрос заголовка ====
 
Во время скачивания передаётся не только сама страница (''тело сообщения'' или на {{англ|body}}), но и техническая информация (''заголовок'' или на {{англ|head}}). Мы ещё не раз будем свидетелями такого поведения в протоколах сети Интернет. В заголовке содержится информация об успешности запроса (код возврата), типе передаваемых данных, кодировке, HTTP-сервере и так далее. Для примера, мы будем производить HTTP-запрос и получать ответ только в виде заголовка. Запись заголовка в файл производить не будем, так как в реальной практике этот приём практически не используется. Сначала «потренируемся на кошках», то есть на классе <code>Net::HTTP</code>:
 
Строка 818 ⟶ 804 :
}</source>
 
Для этого достаточно присвоить результат метода <code>.get</code> двум переменным. Произойдёт присвоение списков, и в первую переменную попадет заголовок, а во вторую  — тело сообщения.
 
Теперь рассмотрим, как выглядит чтение заголовков в методе <code>open</code> библиотеки <code>open-uri</code>:
Строка 826 ⟶ 812 :
p open('http://ru.wikibooks.org/wiki/Ruby').meta</source>
 
Замена метода <code>.read</code> на метод <code>.meta</code> позволяет нам дотянуться до заголовка. Заголовок имеет вид ассоциативного массива (объект класса <code>Hash</code>), где ключом является имя параметра (по стандарту [[w:MIME|MIME]]), а значением — значение параметра. Читать одновременно заголовок и тело сообщения можно вот так:
 
<source lang="ruby">require 'open-uri'
Строка 838 ⟶ 824 :
 
==== Работа через прокси ====
[[w:Прокси-сервер|Прокси-сервер]] — это сервер, перенаправляющий запросы другим серверам. Обычно таковой используется для скрытия истинного [[w:IP-адрес|IP-адреса]] или для контроля за [[w:Трафик|трафиком]].
 
[[w:Прокси-сервер|Прокси-сервер]] – это сервер, перенаправляющий запросы другим серверам. Обычно таковой используется для скрытия истинного [[w:IP-адрес|IP-адреса]] или для контроля за [[w:Трафик|трафиком]].
 
Необходимость HTTP-запроса через прокси возникает, когда соединение с целевым сервером напрямую невозможно (например, администратор сети посчитал, что соединение посредством шлюза даёт ему слишком мало возможностей контроля за трафиком). Использование подобного запроса маловероятно, но необходимо знать о возможности посылки HTTP-запроса через прокси. Начнём с класса <code>Net::HTTP::Proxy</code>:
Строка 864 ⟶ 849 :
 
=== Запускаем свой веб-сервер ===
 
Для того, чтобы создать простейший [[w:Веб-сервер|веб-сервер]] не нужно реализовывать протокол HTTP на уровне [[w:Сокет (программный интерфейс)|сокетов]]. Достаточно знать, какую библиотеку использовать. В нашем случае это будет библиотека [[w:WEBrick|WEBrick]] (http://www.webrick.org), которая уже включена в дистрибутив Ruby. Возможности библиотеки настолько широкие, что для их описания потребуется создание отдельного учебника. Мы рассмотрим лишь часть из них, зато на реальном жизненном примере.
 
Строка 895 ⟶ 879 :
Библиотека <code>WEBrick</code> реализована в виде модуля с одноимённым названием, поэтому до любого класса приходится «дотягиваться» через префикс: <code>WEBrick::</code>.
 
<code>HTTPServer</code>  — это название класса, который используется для создания веб-серверов.
 
Метод <code>.new</code> создаёт экземпляр класса <code>WEBrick::HTTPServer</code>. Методу передаётся два параметра (на самом деле  — один ассоциативный массив с двумя парами):
 
* <code>:DocumentRoot=>'public_html'</code> указывает на то, что веб-сервер будет просматривать директорию <code>./public_html</code> для выдачи html-страниц (и не только) по запросу. Если этот параметр не указывать, то сервер просто не будет работать.
Строка 1044 ⟶ 1028 :
* <code>bosses = { … }</code>
 
Создаётся ассоциативный массив, который будет использоваться при обработке данных. Ключом является имя «переключателя», а значением  — строка, на которую надо этот ключ заменить. Путём такой замены мы будем формировать результат.
 
* <code>require 'webrick'</code>
Строка 1077 ⟶ 1061 :
Запуск сервера.
 
Вот почти такую программу я и презентовал секретарше Анюте. «Почти» потому, что фамилии должны идти строго в определённом порядке (чего нельзя добиться в ассоциативном массиве), но для того, чтобы посмотреть реализацию веб-сервера  — продемонстрированной версии программы должно быть достаточно. Смело меняйте код и создавайте свои веб-сервера!
 
==== Сервлетки в рубиновом соусе ====
 
Мы уже неоднократно упоминали понятие сервлет, но особенно на нём не останавливались. Давайте сейчас рассмотрим типовые сервлеты, которые имеются в стандартной поставке <code>WEBrick</code>
 
Строка 1130 ⟶ 1113 :
end</source>
 
Немного поясню. Первая строчка  — это программа, которая записывает нижеследующий код в папку с библиотеками.
 
Функциональность CGI-сервлета описана в классе <code>WEBrick::HTTPServlet::CGIHandler</code>.