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

Содержимое удалено Содержимое добавлено
м <source> -> <syntaxhighlight> (phab:T237267)
 
Строка 9:
На руках у него уже была серверная часть программы, которая позволяла манипулировать удалённой файловой системой:
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
server = TCPServer.new('localhost', 3000)
while (srv = server.accept)
Строка 44:
end
srv.close
end</sourcesyntaxhighlight>
 
Программка, конечно, хорошая, но сдаётся мне, что она не работает. Первое, на что следует обратить внимание, — это закрытие соединения после выполнения каждой команды. При работе с этим сервером через [[w:Telnet|telnet]] будут возникать определённые проблемы. Давайте предельно упростим специализированный код этой программы и посмотрим на структуру клиент-серверного приложения.
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
TCPServer.open('localhost', 3000){ |server|
if (session = server.accept)
Строка 76:
}
end
}</sourcesyntaxhighlight>
 
Для того, чтобы соединиться с этим сервером, необходимо выполнить команду <code>telnet</code> и набрать <code>o localhost 3000</code>. После успешного соединения можете набирать команды <code>ls</code>, <code>cd</code> или <code>shutdown</code>.
Строка 111:
Несмотря на то, что данный сервер вполне рабочий, у него есть один существенный недостаток — он не работает для нескольких клиентов. Для того, чтобы это реализовать необходимо обрабатывать каждое соединение с клиентом в отдельном потоке. Кто изучал мультипроцессорное программирование, тот понимает о чём речь. Но не стоит сразу кидаться в книжный магазин за необходимой литературой. В составе дистрибутива Ruby уже есть замечательная библиотека <code>gserver</code>, которая как раз и занимается тем, что реализует обработку запросов клиентов в отдельных потоках. Для демонстрации её работы перепишем предыдущую программу под <code>gserver</code>.
 
<sourcesyntaxhighlight lang="ruby">require 'gserver'
class Troyan < GServer
def serve(session)
Строка 144:
troyan.audit = true
troyan.start
troyan.join</sourcesyntaxhighlight>
 
Программа претерпела множество изменений, но зато теперь поддерживаются несколько одновременно работающих клиентов. Давайте рассмотрим подробнее внесённые изменения:
Строка 187:
Итак, давайте наберём клиентский код, который будет соединяться с нашей серверной частью:
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
TCPSocket.open('localhost', 31337){ |client|
2.times{ puts client.gets.chomp }
Строка 197:
puts client.read
client.puts "shutdown"
}</sourcesyntaxhighlight>
 
Всё замечательно, но программа не работает. Не вся, конечно… Она выводит приглашение к диалогу и всё, дальше виснет. Это связано с тем, что используется метод <code>.read</code>, который считывает весь поток целиком, пока не встретит символ EOF. Его-то наш сервер как раз и не передаёт. Не будем пока спешить и править сервер, а применим один приём: будем использовать не метод <code>.read</code>, а метод <code>.sysread(n)</code>. Метод <code>.sysread(n)</code> считывает первых <code>n</code> символов из потока. Так как мы не знаем, сколько нам надо считать символов, то мы зададим в качестве <code>n</code> очень большое число. Например, <code>5000</code>. Если символов в потоке меньше, чем <code>5000</code>, то <code>.sysread(n)</code> считает столько, сколько есть. Эту особенность мы и используем.
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
TCPSocket.open('localhost', 31337){ |client|
2.times{ puts client.gets.chomp }
Строка 211:
puts client.sysread(5000)
client.puts "shutdown"
}</sourcesyntaxhighlight>
 
Уже лучше. По крайней мере, программа работает. Но давайте поразмышляем над ситуацией, которая произошла с методом <code>.read</code>. Если немного подправить сервер и выдавать после каждой передачи этот символ, то программа с <code>.read</code> могла бы с успехом работать. Какова здесь мораль? А мораль в том, что для успешной работы необходимо с сервера передавать сигнал, который означал бы «последняя строка, которую я передаю клиенту». Чтобы клиент не пытался читать данные с сервера, а начинал их передачу. Вполне естественно, что добавление такого сигнала означает модификацию сервера. В качестве сигнала последней строки мы будем использовать строку <code>+ОК</code>. Почему именно такую? Просто видел её где-то, вот и решил использовать. Если хотите, то можете использовать свою строку. Только не забудьте об этом, когда будете писать программу-клиент. Вот модифицированный сервер:
 
<sourcesyntaxhighlight lang="ruby">require 'gserver'
class Troyan < GServer
def serve(session)
Строка 249:
troyan.audit = true
troyan.start
troyan.join</sourcesyntaxhighlight>
 
Была добавлена лишь одна команда (хотя самые внимательные могут заметить, что ещё и метод <code>.print</code> был заменён на <code>.puts</code>) — <code>session.puts "+OK"</code>. Она будет выполнятся после каждой передачи данных от сервера к клиенту. Тем самым мы будем извещать клиента о том, что передача завершается. Теперь перепишем клиент. Необходимо исправить код там, где происходит чтение, чтобы он учитывал строки <code>+OK</code>.
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
TCPSocket.open('localhost', 31337){ |client|
2.times{ puts client.gets.chomp }
Строка 275:
end
client.puts "shutdown"
}</sourcesyntaxhighlight>
 
Стоит отметить, что предложено три варианта обработки строки <code>+OK</code>. Правда, отличаются они лишь в деталях и функционально делают одно и то же. Давайте рассмотрим подробней решения, которые были реализованы в ходе исправления:
Строка 297:
Из всех предложенных вариантов вы вольны выбирать любой. Но мне не нравится вообще весь клиент. Как-то он сильно разросся и теперь выглядит монстрозно. Конечно же, есть возможность загнать чтение в отдельный метод, но мы этого делать не будем. Хотя, где наша не пропадала?! Давайте вынесем код отсылки команды и получения ответа в отдельный код. Естественно, что это будет метод для класса <code>TCPSocket</code> (который мы будем расширять). Назовем мы его <code>.cmd</code>.
 
<sourcesyntaxhighlight lang="ruby">class TCPSocket
def cmd(command, regexp = /^\+OK/)
self.puts command
Строка 304:
end
end
end</sourcesyntaxhighlight>
 
Итак, теперь все экземпляры класса <code>TCPSocket</code> приобрели метод <code>.cmd</code>, который отсылает команды и принимает результат. Непонятными могут быть следующие моменты:
Строка 318:
Теперь остаётся исправить клиент и посмотреть на него в действии:
 
<sourcesyntaxhighlight lang="ruby">TCPSocket.open('localhost', 31337){ |client|
2.times{ puts client.gets.chomp }
client.cmd("ls"){ |str| puts str }
Строка 324:
client.cmd("ls"){ |str| puts str }
client.cmd("shutdown"){ |str| puts str }
}</sourcesyntaxhighlight>
 
Обратите внимание на код:
 
<sourcesyntaxhighlight lang="ruby">client.cmd("ls"){ |str| puts str }</sourcesyntaxhighlight>
 
Метод <code>.cmd</code> работает как итератор. Последовательно передавая пришедшие строки в замыкание для дальнейшей обработки.
Строка 338:
Это всё потому, что передача команды <code>shutdown</code> не подразумевает ответа. А это неправильно. В очередной раз исправим сервер, чтобы избавится от этой ошибки:
 
<sourcesyntaxhighlight lang="ruby">require 'gserver'
class Troyan < GServer
def serve(session)
Строка 374:
troyan.audit = true
troyan.start
troyan.join</sourcesyntaxhighlight>
 
На этом можно было бы и закончить, если бы не одно «но»: метод <code>.cmd</code> уже реализован в рамках класса <code>Net::Telnet</code>. И не переписать наш клиент под этот класс было бы неправильно. Переписываем:
 
<sourcesyntaxhighlight lang="ruby">require 'net/telnet'
client = Net::Telnet.new('Host'=>'localhost', 'Port'=>31337, "Prompt"=>/^\+OK/n)
client.cmd("ls"){ |str| puts str }
client.cmd("cd .."){ |str| puts str }
client.cmd("ls"){ |str| puts str }
client.close</sourcesyntaxhighlight>
 
Вот теперь уж точно всё!
Строка 389:
Вскоре, после публикации данной главы в учебнике, Geka прислал клиентскую часть, которую он реализовал в две строчки:
 
<sourcesyntaxhighlight lang="ruby">system("telnet " + gets)
loop{ system(gets){ |str| puts str } }</sourcesyntaxhighlight>
 
Способ не совсем честный, но нет причин о нём не рассказать. Данный способ использует метод <code>system</code>, который вызывает внешнюю программу (в данном случае <code>telnet</code>). Далее все введенное с клавиатуры уходит в программу <code>telnet</code>, а выдаваемое на экран берётся из результата работы этой программы.
Строка 408:
Теперь, собственно, сама программа:
 
<sourcesyntaxhighlight lang="ruby">require 'webrick'
server = WEBrick::HTTPServer.new(:Port=>8080)
server.mount_proc('/'){ |req, resp|
Строка 419:
}
 
server.start</sourcesyntaxhighlight>
 
Рассмотрим код более подробно.
Строка 482:
Давайте посмотрим, как может выглядеть наша программа в которой используется ERB-шаблон:
 
<sourcesyntaxhighlight lang="ruby">require 'webrick'
require 'erb'
server = WEBrick::HTTPServer.new(:Port=>8080)
Строка 494:
resp.body = ERB.new(template).result
}
server.start</sourcesyntaxhighlight>
 
Что изменилось? Изменений немного, но они все же есть:
Строка 517:
Файл с ERB-шаблоном (<code>index.html</code>) будет выглядеть следующим образом:
 
<sourcesyntaxhighlight lang="rails"><html>
<body>
<center>
Строка 526:
</center>
</body>
</html></sourcesyntaxhighlight>
 
Переменную шаблон мы убираем, а вместо неё вставим считывание файла c ERB-шаблоном(<code>index.html</code>).
 
<sourcesyntaxhighlight lang="ruby">require 'webrick'
require 'erb'
server = WEBrick::HTTPServer.new(:Port=>8080)
Строка 538:
resp.body = ERB.new(IO.read('index.html')).result
}
server.start</sourcesyntaxhighlight>
 
Вот, уже другое дело! Можно было бы этим и ограничиться, если бы в библиотеке WEBrick отсутствовал бы ERB-сервлет… Но он есть!
Строка 545:
Если ERB-шаблон подключать, как ERB-сервлет, то программа существенно упрощается за счет того, что логику, которая отвечает за формирование данных можно вынести в шаблон. Чтобы это ощутить, достаточно взглянуть на новую версию нашей программы:
 
<sourcesyntaxhighlight lang="ruby">require 'webrick'
server = WEBrick::HTTPServer.new(:Port=>8080)
server.mount('/', WEBrick::HTTPServlet::ERBHandler, 'index.html')
server.start</sourcesyntaxhighlight>
 
Самые внимательные читатели заметили, что строка <code>require 'erb'</code> волшебным образом испарилась. Связано это с тем, что реализация ERB-сервлета уже подключает библиотеку ERB.
Строка 554:
Но чтобы добиться столь существенного уменьшения кода программы, пришлось немного изменить файл <code>index.html</code> (с ERB-шаблоном):
 
<sourcesyntaxhighlight lang="rails"><% File.open('notepad.txt', 'w'){ |f| f.write query["text"] } if query["text"] %>
<html><body><center><form method=post>
<textarea name="text" rows="4" cols="40"><%=IO.read('notepad.txt')%></textarea><br/>
<input type="submit" name="update" value="Сохранить" />
</form></center></body></html></sourcesyntaxhighlight>
 
Как можно заметить, в самое начало шаблона был добавлен тег, который осуществляет сохранение содержимого переменной <code>text</code> в файл <code>notepad.txt</code>. Код тега был перенесён из программы практически один к одному. За одним только исключением: к переменной <code>text</code> мы теперь обращаемся через переменную <code>query</code>, а не через <code>req.query</code>.
Строка 576:
Открываем новую серию статей, которые будут рассказывать про использование встроенных библиотек Ruby. Первая статья будет посвящена написанию утилиты <code>ping</code> (в очень упрощённой форме). Смотрим в стандартную библиотеку и обнаруживаем файлик <code>ping.rb</code>. Смотрим в него и обнаруживаем метод <code>pingecho</code>. Метод используется следующим образом:
 
<sourcesyntaxhighlight lang="ruby">require 'ping'
host = ARGV[0] || "localhost"
printf("%s alive? - %sn", host, Ping::pingecho(host, 5))</sourcesyntaxhighlight>
 
Данный метод имеет один маленький недостаток. Он не отслеживает никаких ошибок кроме <code>Timeout::Error и Errno::ECONNREFUSED</code>. Меня это немного смутило и поэтому я убрал явное указание на <code>Timeout</code>. Получился примерно такой метод:
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
require 'timeout'
def ping(host, service = "echo", timeout = 5)
Строка 595:
end
end
p ping(ARGV[0] || "localhost")</sourcesyntaxhighlight>
 
Итак, давайте разберём, что делает наш метод. Он создаёт соединение посредством класса <code>TCPSocket</code> и тут же закрывает его. Если соединение проходит слишком долго (хост не существует в сети) или произошла какая-то другая ошибка (не поддерживается протокол или ещё что-то), то метод возвращает <code>false</code>. Если удалённый хост явно отверг наш запрос или принял его, то мы возвращаем <code>true</code>.
Строка 608:
Её я решил писать в логи следующим образом. Каждый день будет создаваться файл лога и каждый час в него будет писаться информация о работе службы. В качестве планировщика заданий использовался виндовый [http://www.nncron.ru/ Cron], который запускал мою программу в нужное время. Для начала я написал программу, которая соединяется со службой SMTP и получает от него баннер:
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
 
request = ""
Строка 618:
File.open(Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY){ |f|
f.puts("#{sprintf('%02d', Time.now.hour)} | #{end_time-begin_time} | #{request}")
}</sourcesyntaxhighlight>
 
Как и следовало ожидать, программа не работала. Она подвисала и не хотела ничего писать в файл. Висела она на строчке:
 
<sourcesyntaxhighlight lang="ruby">request = t.gets.chomp</sourcesyntaxhighlight>
 
Чтобы разобраться с проблемой, пришлось читать книжку. Слава богу, что под рукой оказалась книга [http://books.dore.ru/bs/f1bid1824.html TCP/IP. Учебный курс]. В ней на странице 345 чёрным по серому была начертана схема взаимодействия SMTP протокола. Как оказалось, чтобы получить баннер от службы, надо послать команду <code>NOOP</code>.
Строка 630:
Переписываем наш фрагмент программы.
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
 
request = ""
Строка 641:
File.open(Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY){ |f|
f.puts("#{sprintf('%02d', Time.now.hour)} | #{end_time-begin_time} | #{request}")
}</sourcesyntaxhighlight>
 
Великолепно! Программа работает… но иногда зависает. И тогда меня посетила ещё одна мысль: на соединение отводить всего одну секунду (не уложился — сам дурак). Если соединение зависало, то в файл записывалось <code>timeout</code>. Чтобы «отводить время на выполнение операции» нужно задействовать библиотеку <code>timeout</code>. Она у меня входила в состав дистрибутива. Итак, переписываем нашу программу:
 
<sourcesyntaxhighlight lang="ruby">require 'timeout'
require 'socket'
 
Строка 663:
File.open(Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY){ |f|
f.puts("#{sprintf('%02d', Time.now.hour)} | #{beachmark} | #{request}")
}</sourcesyntaxhighlight>
 
Всё бы хорошо, но вот <code>beachmark</code> хотелось бы считать «по-взрослому», а именно при помощи пакета <code>benchmark</code>. И снова переписываем код:
 
<sourcesyntaxhighlight lang="ruby">require 'timeout'
require 'socket'
require 'benchmark'
Строка 687:
File.open(PATH_LOG_SMTP + Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY){ |f|
f.puts("#{sprintf('%02d', Time.now.hour)} | #{beachmark} | #{request}")
}</sourcesyntaxhighlight>
 
И тут мы замечаем, что <code>Benchmark</code> очень многословен. Он выдаёт информацию в виде:
Строка 695:
Нам нужен только <code>real</code>. Всё остальное — для более детального анализа. Поэтому немного доработаем результат замыкания <code>measure</code>:
 
<sourcesyntaxhighlight lang="ruby">beachmark = Benchmark.measure{
timeout(1){
t = TCPSocket.open('mail.scli.ru', 'smtp')
Строка 702:
t.close
}
}.real</sourcesyntaxhighlight>
 
Или можно просто вместо <code>Benchmark.measure</code> использовать <code>Benchmark.realtime</code>. Теперь надо разделить ошибки по таймауту и ошибки соединения. Для этого надо добавить ещё один блок <code>;rescue</code>. Кроме того, мне не понадобится всё сообщение от службы. Мне и кода сообщения достаточно. Смотрим, что получилось:
 
<sourcesyntaxhighlight lang="ruby">require 'socket'
require 'benchmark'
require 'timeout'
Строка 729:
File.open(Time.now.strftime('d:/logs/smtp/%Y_%m_%d.smtp'), File::APPEND | File::CREAT | File::WRONLY){ |f|
f.puts("#{sprintf('%02d', Time.now.hour)} | #{beachmark} | #{request}")
}</sourcesyntaxhighlight>
 
А теперь вопрос: как переписать программу так, чтобы она могла тестировать не только SMTP, но и [[w:HTTP|HTTP]], [[w:FTP|FTP]], [[w:POP3|POP3]] и так далее? Это уже для самостоятельного изучения.
Строка 748:
Каждый раз при запуске программы, мы скачиваем заглавную страницу сайта и выдираем оттуда даты и заголовки новостей. Потом читаем файл с подобным же набором даных. Читаем весь файл, хотя можно читать только последние n строк. Но мы будем создавать каждый месяц новый файл, и поэтому особой загрузки памяти происходить не должно. Далее сравниваем эти два набора данных и с помощью пересечения и вычитания множеств мы получаем те данные, которых до сих пор нет в файле. Как раз эти данные мы и добавляем в конец файла. Всё. Теперь код, который выполняет поставленную задачу:
 
<sourcesyntaxhighlight lang="ruby">require 'net/http'
 
h = Net::HTTP.new('www.minjust.ru', 80)
Строка 760:
from_file = f.readlines.map{ |str| str.chomp }.compact
f.puts((from_inet - (from_file & from_inet)).sort)
}</sourcesyntaxhighlight>
 
В результате мы получаем файлы, которые могут быть с лёгкостью использованы для составления отчётов.
Строка 769:
Для скачивания HTML-страниц обычно используется протокол HTTP. Адрес страницы задаётся в виде URL. В Ruby существует несколько способов скачать страницу по протоколу HTTP. Начнём с самого древнего — класса <code>Net::HTTP</code> (древнее только Socket’ы, но про них мы умолчим). Для определённости мы будем скачивать этот учебник и сохранять его в виде файла <tt>RubyBook.html</tt>.
 
<sourcesyntaxhighlight lang="ruby">require 'net/http'
 
Net::HTTP.start('ru.wikibooks.org'){ |http|
Строка 775:
file.write http.get('/wiki/Ruby').body
}
}</sourcesyntaxhighlight>
 
Недостатком использования класса <code>Net::HTTP</code> является то, что URL разбивается на две части: адрес сервера (<code>ru.wikibooks.org</code>) и адрес страницы (<code>/wiki/Ruby</code>). Такое разбиение удобно только в том случае, когда необходимо скачивать несколько HTML-страниц с одного сервера. А вот как раз такие ситуации возникают крайне редко. Чаще приходится запрашивать страницы с различных серверов. Для этих целей и был придуман метод <code>open</code> из библиотеки <code>open-uri</code>. Он самостоятельно разбирает URL и производит нужный запрос (не только HTTP).
 
<sourcesyntaxhighlight lang="ruby">require 'open-uri'
 
File.open('RubyBook.html', 'w'){ |file|
file.write open('http://ru.wikibooks.org/wiki/Ruby').read
}</sourcesyntaxhighlight>
 
Обратите внимание, что необходимо указывать полный URL с указанием протокола (в данном случае <code>http://</code>).
Строка 790:
Во время скачивания передаётся не только сама страница (''тело сообщения'' или на {{англ|body}}), но и техническая информация (''заголовок'' или на {{англ|head}}). Мы ещё не раз будем свидетелями такого поведения в протоколах сети Интернет. В заголовке содержится информация об успешности запроса (код возврата), типе передаваемых данных, кодировке, HTTP-сервере и так далее. Для примера, мы будем производить HTTP-запрос и получать ответ только в виде заголовка. Запись заголовка в файл производить не будем, так как в реальной практике этот приём практически не используется. Сначала «потренируемся на кошках», то есть на классе <code>Net::HTTP</code>:
 
<sourcesyntaxhighlight lang="ruby">require 'net/http'
 
Net::HTTP.start('ru.wikibooks.org'){ |http|
p http.head('/wiki/Ruby')
}</sourcesyntaxhighlight>
 
Как и обещалось ранее, вывод заголовка мы делаем на экран. А запрос заголовка, вместо тела сообщения, осуществляется простой заменой метода <code>.get</code> на метод <code>.head</code>. А как тогда получить заголовок и тело сообщения одновременно? Очень просто:
 
<sourcesyntaxhighlight lang="ruby">require 'net/http'
 
Net::HTTP.start('ru.wikibooks.org'){ |http|
head, body = http.get('/wiki/Ruby')
}</sourcesyntaxhighlight>
 
Для этого достаточно присвоить результат метода <code>.get</code> двум переменным. Произойдёт присвоение списков, и в первую переменную попадет заголовок, а во вторую — тело сообщения.
Строка 808:
Теперь рассмотрим, как выглядит чтение заголовков в методе <code>open</code> библиотеки <code>open-uri</code>:
 
<sourcesyntaxhighlight lang="ruby">require 'open-uri'
 
p open('http://ru.wikibooks.org/wiki/Ruby').meta</sourcesyntaxhighlight>
 
Замена метода <code>.read</code> на метод <code>.meta</code> позволяет нам дотянуться до заголовка. Заголовок имеет вид ассоциативного массива (объект класса <code>Hash</code>), где ключом является имя параметра (по стандарту [[w:MIME|MIME]]), а значением — значение параметра. Читать одновременно заголовок и тело сообщения можно вот так:
 
<sourcesyntaxhighlight lang="ruby">require 'open-uri'
 
open('http://ru.wikibooks.org/wiki/Ruby/'){ |f|
p f.meta
p f.read
}</sourcesyntaxhighlight>
 
Мы использовали свойство метода <code>.open</code> прикреплять к себе замыкание.
Строка 828:
Необходимость HTTP-запроса через прокси возникает, когда соединение с целевым сервером напрямую невозможно (например, администратор сети посчитал, что соединение посредством шлюза даёт ему слишком мало возможностей контроля за трафиком). Использование подобного запроса маловероятно, но необходимо знать о возможности посылки HTTP-запроса через прокси. Начнём с класса <code>Net::HTTP::Proxy</code>:
 
<sourcesyntaxhighlight lang="ruby">require 'net/http'
 
Net::HTTP::Proxy('you.proxy.host', 8808).start('ru.wikibooks.org'){ |http|
p http.get('/wiki/Ruby').body
}</sourcesyntaxhighlight>
 
Добавив всего лишь небольшой фрагмент кода, мы получили работу через прокси. В нашем случае использовался прокси-сервер с адресом <code>you.proxy.host</code>, который предоставляет прокси-доступ через порт <code>8808</code>.
Строка 838:
Теперь посмотрим, как эта же самая функциональность можеть быть реализована с использованием метода <code>open</code> (из библиотеки <code>open-uri</code>).
 
<sourcesyntaxhighlight lang="ruby">require 'open-uri'
 
p open('http://ru.wikibooks.org/wiki/Ruby', :proxy=>'http://you.proxy.host:8808/').read</sourcesyntaxhighlight>
 
Добавился второй параметр (ассоциативный массив с единственной парой) в вызове метода <code>open</code>. К слову сказать, во втором параметре можно указать множество параметров запроса (например, название браузера).
Строка 857:
Для того, чтобы проверить работает ли библиотека <code>WEBrick</code> я выполнил следующий код:
 
<sourcesyntaxhighlight lang="ruby">require 'webrick'
 
WEBrick::HTTPServer.new(:DocumentRoot=>'public_html', :Port=>8080).start</sourcesyntaxhighlight>
 
После успешного запуска сервера, я запустил [[w:Mozilla Firefox|Firefox]] и набрал адрес: <code>http://localhost:8080/</code>. И тут я узнал, что директория <code>public_html</code> пуста. Горевал я по этому поводу не долго и быстро создал файл <code>public_html/index.html</code> примерно следующего содержания:
 
<sourcesyntaxhighlight lang="html4strict"><html><body><h1>Сервер работает!</h1></body></html></sourcesyntaxhighlight>
 
После обновления страницы на экране красовалась крупная надпись: «Сервер работает!» Всё! Мы с вами написали свой первый веб-сервер.
Строка 871:
Настало время разобраться в том, как это всё работает:
 
<sourcesyntaxhighlight lang="ruby">require 'webrick'</sourcesyntaxhighlight>
 
Подключение библиотеки <code>WEBrick</code>. Ничего интересного и неожиданного здесь не произошло.
 
<sourcesyntaxhighlight lang="ruby">WEBrick::HTTPServer.new(:DocumentRoot=>'public_html', :Port=>8080).start</sourcesyntaxhighlight>
 
Библиотека <code>WEBrick</code> реализована в виде модуля с одноимённым названием, поэтому до любого класса приходится «дотягиваться» через префикс: <code>WEBrick::</code>.
Строка 892:
Вернёмся к заказанной мне программе. Она будет состоять из двух частей: обычная html-страница (<code>index.html</code>) и [[w:Сервлет|сервлет]] (<code>/input</code>). Страница <tt>public_html/index.html</tt> будет содержать форму, в которую будут вводится исходные данные. Её задачей будет формирование запроса для сервлета. Сервлет <code>/input</code> будет получать номера подразделений, а выдавать их названия и ФИО действующих начальников. Вот код <code>public_html/index.html</code>:
 
<sourcesyntaxhighlight lang="html4strict"><html><body><form action='/input' method='post'><div style="align:center">
<table border=0 bgcolor=#000000 width=350><tr><td><table border=0 bgcolor=#CCCCFF width=100%>
<tr><td align=right>Лит-1 - 01</td><td><input type="checkbox" name="check_01"></td>
Строка 939:
<tr><td align=right>Поленов</td><td><input type="checkbox" name="check_011"></td>
<td><input type="checkbox" name="check_012"></td><td>Копылов</td></tr>
</table></td></tr></table></div></form></body></html></sourcesyntaxhighlight>
 
Этот код является результатом программы-генератора, которую я не привожу здесь, чтобы не отвлекать внимание читателя от более интересной темы.
Строка 969:
Теперь рассмотрим серверную часть программы (сервлет <code>/input</code>), которая будет обрабатывать запросы (сформированные файлом <code>public/index.html</code>). Сервлет является частью сервера. Поэтому листинг сервера будет одновременно и листингом сервлета.
 
<sourcesyntaxhighlight lang="ruby">bosses = {
'check_01'=>"\tНачальнику Лит-1\tБокову Ю.В.",
'check_02'=>"\tНачальнику ГЛЦЧ\tНазарову А.В.",
Строка 1022:
</textarea><br/><input type='submit' value='Повторим?'></form></div></body></html>!
}
server.start</sourcesyntaxhighlight>
 
Начинаем разбираться с кодом веб-сервера:
Строка 1074:
Реализует взаимосвязь запроса с CGI-приложением, то есть считывается первая строка и вытаскивается оттуда путь к интерпретатору. Далее производится запуск интерпретатора, которому параметром передаётся вызванный файл. Строка для CGI-приложений на Ruby будет выглядеть приемерно так:
 
<sourcesyntaxhighlight lang="bash">#!/usr/bin/ruby</sourcesyntaxhighlight>
 
для операционных систем семейства Unix
Строка 1084:
Кстати, я столкнулся с тем, что под Windows CGI-сервлеты отказывались работать. Это связано с тем, что <code>WEBrick</code> считает, что для того, чтобы запустить скрипт достаточно просто его вызывать (<code>./sctipt_name</code>) и он сам запустится. Понятное дело, что в Windows такое работать не будет. Поэтому мне пришлось немножко переписать часть библиотеки WEBrick, которая отвечает за запуск CGI-программ. Для того, чтобы мои изменение стали доступны и вам, я написал небольшую программку, которая устанавливает себя куда надо:
 
<sourcesyntaxhighlight lang="ruby">File.open(Config::CONFIG['rubylibdir']+'/webrick/httpservlet/cgi_runner.rb', 'w'){|file|
file.puts IO.read($0).split('#'*10).last
}
Строка 1111:
else
exec(ENV["SCRIPT_FILENAME"])
end</sourcesyntaxhighlight>
 
Немного поясню. Первая строчка — это программа, которая записывает нижеследующий код в папку с библиотеками.
Строка 1127:
Для того, чтобы было проще пользоваться всеми видами сервлетов WEBrick я написал небольшую программку, которая создаёт метод <code>mount_file</code> (для файлового сервлета), <code>mount_erb</code> (для ERB-сервлета) и <code>mount_cgi</code> (для CGI-сервлета). Как вы могли заметить, <code>mount_proc</code> уже существует (собственно, его название и послужило прототипом для остальных). Вот эта программа:
 
<sourcesyntaxhighlight lang="ruby">require 'webrick'
module WEBrick
class HTTPServer
Строка 1134:
}
end
end</sourcesyntaxhighlight>
 
Теперь можно использовать эти сервлеты следующим образом:
 
<sourcesyntaxhighlight lang="ruby">require 'webrick'
module WEBrick
class HTTPServer
Строка 1151:
server.mount_cgi('/hello', 'cgi-bin/hello.exe')
server.mount_cgi('/hello.cgi', 'cgi-bin/hello.rb')
server.start</sourcesyntaxhighlight>
 
Ну вот и всё, что можно сказать по сервлетам.