Блог веб-программиста

Март 25, 2009

Заметка одиннадцатая(сетевые возможности)

Filed under: ruby — krimik @ 13:08
Tags: , , ,

Сетевые возможности ruby мы рассмотри на примере написания web-servera.

Итак, начнём=)

Для начала нам понадобится http://www.ruby-doc.org/core/ и далее, при написании хороших программ – всегда будем смотреть именно туда, чтобы не изобретать велосипед. Многие читатели моего блога, думаю, знаю про WebRick( это уже написанный полнофункциональный веб сервер на языке ruby), о нём можно почитать  здесь. Но мы напишем свой web-сервер, дабы показать возможности ruby.

Нам понадобятся две библиотеки( ‘socket’ и ‘logger’). Первая, как вы догадались для работы с сокетами, вторая для логгирования.

Назовём наш класс HttpServer и добавим переменные инициализации:

  1. class HttpServer
  2. def initialize(session, request, basepath)
  3. @session = session
  4. @request = request
  5. @basepath = basepath
  6. end
  7. end

session – переменная, в которую пишется наша сессия.

request – переменная, куда пишется запрос, который приходит от браузера.

basepath – каталог, где лежит наш сайт.

Начнём разбирать “путь запроса файла”. Почитать про заголовки, можно здесь

  1. def getfullpath()
  2. file_name = @request =~ /^GET\s+(\S+)/i ? $1 : “/”
  3. file_name = File.expand_path(file_name, @basepath)
  4. file_name << “index.html” if File.directory?(file_name)
  5. return file_name
  6. end

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

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

А если у нас запрашивается не файл, а папка? – выход прост – проверяем, запрашивается ли дирректория? – если да, то отдаём “index.html”.

Теперь надо расмотреть то, как мы будем отдавать файлы браузеру. Для этого нам надо почитать ещё это.

  1. def serve()
  2. temp = getfullpath()
  3. begin
  4. if File.exist?(@basepath + temp) and File.file?(@basepath + temp)
  5. @session.puts “HTTP/1.1 200 OK\r\nDate: #{Time.now.localtime.strftime(“%a, %d %b %Y %H:%M:%S GTM“)}\r\nServer: KriM\r\nContent-type: #{getcontenttype(temp)}; charset=utf-8\r\n\r\n”
  6. @session.puts IO.read(@basepath + temp)
  7. @session.puts Time.now.localtime.strftime(“%a, %d %b %Y %H:%M:%S GTM”)
  8. else
  9. @session.puts “HTTP/1.1 404 Object Not Found\r\nServer: KriM\r\n\r\n”
  10. @session.puts “page not found, 404″
  11. end
  12. ensure
  13. @session.close
  14. end
  15. end

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

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

Если файл не найден на жёстком диске, то надо вывести ошибку 404, сказать пользователю о том, что файл не найден на сервере.

Ну и закрываем сессию=) Если вы читали предыдущую заметку, то думаю не надо объяснять зачем нужна конструкция вида begin – ensure – end.

Рассмотрим функцию getcontenttype. Она очень простая, получаем тип запрашиваемого файла, создаём хэш массив(смотрим предыдущие заметки) с типами файлов(ключ) и возвращаемым типом контента(значение)

  1. def getcontenttype(path)
  2. ext = File.extname(path)
  3. type = {
  4. “.html” => “text/html”,
  5. “.htm” => “text/plain”,
  6. “.txt” => “text/plain”,
  7. “.css” => “text/css”,
  8. “.jpeg” => “image/jpeg”,
  9. “.jpg” => “image/jpeg”,
  10. “.gif” => “image/gif”,
  11. “.bmp” => “image/bmp”,
  12. “.rb” => “text/plain”,
  13. “.xml” => “text/xml”,
  14. “.xsl” => “text/xml”
  15. }
  16. return type[ext] || “application/octet-stream”
  17. end

Объясню лишь последнюю строчку, возвращаем значение по ключу, или возвращаем “application/octet-stream” , если такого ключа нету.

Всё, класс написан=)

  1. class HttpServer
  2. def initialize(session, request, basepath)
  3. @session = session
  4. @request = request
  5. @basepath = basepath
  6. end
  7. def getfullpath()
  8. file_name = @request =~ /^GET\s+(\S+)/i ? $1 : “/”
  9. file_name = File.expand_path(file_name, @basepath)
  10. file_name << “index.html” if File.directory?(file_name)
  11. return file_name
  12. end
  13. def serve()
  14. temp = getfullpath()
  15. begin
  16. if File.exist?(@basepath + temp) and File.file?(@basepath + temp)
  17. @session.puts “HTTP/1.1 200 OK\r\nDate: #{Time.now.localtime.strftime(“%a, %d %b %Y %H:%M:%S GTM“)}\r\nServer: KriM\r\nContent-type: #{getcontenttype(temp)}; charset=utf-8\r\n\r\n”
  18. @session.puts IO.read(@basepath + temp)
  19. @session.puts Time.now.localtime.strftime(“%a, %d %b %Y %H:%M:%S GTM”)
  20. else
  21. @session.puts “HTTP/1.1 404 Object Not Found\r\nServer: KriM\r\n\r\n”
  22. @session.puts “page not found, 404″
  23. end
  24. ensure
  25. @session.close
  26. end
  27. end
  28. def getcontenttype(path)
  29. ext = File.extname(path)
  30. type = {
  31. “.html” => “text/html”,
  32. “.htm” => “text/plain”,
  33. “.txt” => “text/plain”,
  34. “.css” => “text/css”,
  35. “.jpeg” => “image/jpeg”,
  36. “.jpg” => “image/jpeg”,
  37. “.gif” => “image/gif”,
  38. “.bmp” => “image/bmp”,
  39. “.rb” => “text/plain”,
  40. “.xml” => “text/xml”,
  41. “.xsl” => “text/xml”
  42. }
  43. return type[ext] || “application/octet-stream” unless type.key?(ext)
  44. end
  45. end

Рассмотрим теперь логирование и запуск сервера.

  1. $log = Logger.new(‘server.log’)
  2. base_path = “/home/krim/web”
  3. server = TCPServer.new(‘localhost’, 9191)
  4. loop do
  5. session = server.accept
  6. request = session.gets
  7. logs = “\n===================================================\n”
  8. logs +=  “#{session.peeraddr[2]} (#{session.peeraddr[3]})\n”
  9. logs += “\n#{request}”
  10. $log.info(logs)
  11. puts logs
  12. Thread.start(session, request) do |session, request|
  13. HttpServer.new(session, request, base_path).serve()
  14. end
  15. end

Первые три строчки можно отнести к разряду “конфига”=). Создаём файл, куда будет записываться лог наших запросов и т.д., в base_path запишем путь до нашего каталога с сайтом, ну а в server запишем наш сокет на 9191 порту(вы можете выбрать любой другой, свободный).

Создаём бесконечный цикл, в session мы будем работать с нашей сессией, в request пишем запросы, получаемые от браузера, записываем то, что хотим логировать в переменную logs и пишем в файл server.log с помощью $log.info(logs) + выводим в консоль, то, что залогировали.

Далее опять идём в документаци и смотрим тут.

Смотрим на метод start, аналогичен new. Запускаем поток и сам сервер=). Всё готово=)

Полный код веб-сервера:


  1. require ‘socket’
  2. require ‘logger’
  3. class HttpServer
  4. def initialize(session, request, basepath)
  5. @session = session
  6. @request = request
  7. @basepath = basepath
  8. end
  9. def getfullpath()
  10. file_name = @request =~ /^GET\s+(\S+)/i ? $1 : “/”
  11. file_name = File.expand_path(file_name, @basepath)
  12. file_name << “index.html” if File.directory?(file_name)
  13. return file_name
  14. end
  15. def serve()
  16. temp = getfullpath()
  17. begin
  18. if File.exist?(@basepath + temp) and File.file?(@basepath + temp)
  19. @session.puts “HTTP/1.1 200 OK\r\nDate: #{Time.now.localtime.strftime(“%a, %d %b %Y %H:%M:%S GTM“)}\r\nServer: KriM\r\nContent-type: #{getcontenttype(temp)}; charset=utf-8\r\n\r\n”
  20. @session.puts IO.read(@basepath + temp)
  21. @session.puts Time.now.localtime.strftime(“%a, %d %b %Y %H:%M:%S GTM”)
  22. else
  23. @session.puts “HTTP/1.1 404 Object Not Found\r\nServer: KriM\r\n\r\n”
  24. @session.puts “page not found, 404″
  25. end
  26. ensure
  27. @session.close
  28. end
  29. end
  30. def getcontenttype(path)
  31. ext = File.extname(path)
  32. type = {
  33. “.html” => “text/html”,
  34. “.htm” => “text/plain”,
  35. “.txt” => “text/plain”,
  36. “.css” => “text/css”,
  37. “.jpeg” => “image/jpeg”,
  38. “.jpg” => “image/jpeg”,
  39. “.gif” => “image/gif”,
  40. “.bmp” => “image/bmp”,
  41. “.rb” => “text/plain”,
  42. “.xml” => “text/xml”,
  43. “.xsl” => “text/xml”
  44. }
  45. return type[ext] || “application/octet-stream” unless type.key?(ext)
  46. end
  47. end
  48. $log = Logger.new(‘server.log’)
  49. base_path = “/home/krim/web”
  50. server = TCPServer.new(‘localhost’, 9191)
  51. loop do
  52. session = server.accept
  53. request = session.gets
  54. logs = “\n===================================================\n”
  55. logs +=  “#{session.peeraddr[2]} (#{session.peeraddr[3]})\n”
  56. logs += “\n#{request}”
  57. $log.info(logs)
  58. puts logs
  59. Thread.start(session, request) do |session, request|
  60. HttpServer.new(session, request, base_path).serve()
  61. end
  62. end

1 комментарий »

  1. В Google Chrome 2 почему-то CSS не рендерится; поправьте, пожалуйста, а то смотрится странно)

    комментарий от Ivan — Сентябрь 29, 2009 @ 09:39 | Ответить


RSS-лента комментариев к этой записи. URI для обратной ссылки

Добавить комментарий

Fill in your details below or click an icon to log in:

Логотип WordPress.com

You are commenting using your WordPress.com account. Log Out / Изменить )

Фотография Twitter

You are commenting using your Twitter account. Log Out / Изменить )

Фотография Facebook

You are commenting using your Facebook account. Log Out / Изменить )

Connecting to %s

Тема: Rubric. Блог на WordPress.com.

Follow

Get every new post delivered to your Inbox.