NginxでのRubyの統合





Nginx + Luaのよく知られた束は、多くの記事を含め、かなり長い間存在しています。 しかし、時は止まりません。 約1年前、RubyをNginxに統合するモジュールの最初のバージョンが登場しました。



ムルビー



統合には、本格的なRubyではなく、他のアプリケーションやデバイスなどに組み込まれるように設計されたサブセットが選択されました。 いくつかの制限がありますが、それ以外は完全なRubyです。 プロジェクトはMRubyと呼ばれます 。 現在、バージョン1.0.0、つまり 安定していると見なされます。

MRubyでは、実行時に他のファイルを添付することはできないため、プログラム全体を1つのファイルに含める必要があります。 同時に、プログラムをバイトコードに変換して実行することもできますが、これはパフォーマンスに良い影響を与えます。

なぜなら 他のファイルをロードする方法はありません。その場合、既存のgemはそれに適していません。 機能を拡張するために、独自の形式が使用されます。これは、一部の場所ではCコードとRubyの両方です。 これらのモジュールは、コンパイル時にライブラリ自体と一緒にアセンブルされ、その不可欠な部分です。 ファイルやネットワークなどを操作するためのさまざまなデータベースへのバインダーがあります。 完全なリストはサイトで入手できます。

また、このエンジンをNginxに統合できるモジュールがありますが、これは特に興味深いものでした。



ngx_mruby



だから知ってくださいngx_mruby 。 rubyスクリプトをnginxに接続するためのモジュール。 Luaバージョンと同様の機能を備えています。 要求処理のさまざまな段階で操作を実行できます。



モジュールは非常に簡単に構築され、サイトには詳細な手順があります。 アセンブリに煩わされたくない場合は、完成したパッケージをダウンロードできます。

http://mruby.ajieks.ru/st/nginx_1.4.4-1~mruby~precise_amd64.deb

このアセンブリのMRubyには、次の追加モジュールが含まれています。



ご覧のとおり、仕事に必要なものはほぼすべてあります。 このモジュールがAPIで見つけられなかった唯一のものは、外部でリクエストを行う機能でした。 ほとんどの場合、拡張機能として実装し、nginx APIをバインドする必要があります。



著者はテスト付きの美しいグラフを表示していますが、環境の構成は見つかりませんでした。 したがって、私はそれを美しさに適用するだけです:

画像



使ってみよう



そのため、サーバーは既にインストールされています。 すべてが機能し、静的が与えられます。 これにダイナミクスを少し追加します。

例として、追加のサーバーアプリケーションなしで、Markdownマークアップを解析し、HTMLでレンダリングするタスクを選択しました。 Rubyソースの行番号付けも同様です。

これを行うには、sinatraリポジトリのクローンを作成し、nginxを設定してタスクを解決します。



マークダウン


マークアップを処理するには、アセンブリに接続されたmruby-discountモジュールを使用します。 マークアップを操作するためのシンプルなクラスを提供します。 同じ名前のCライブラリに基づいているのは、パフォーマンスの問題が特に問題になるとは思わないからです。

まず、要求されたファイルをディスクから読み取り、処理してユーザーに提供するプログラムを作成します。

r = Nginx::Request.new m = Discount.new("/st/style.css", "README") filename = r.filename filename = File.join(filename, 'README.md') if filename.end_with?('/') markdown = File.exists?(filename) ? File.read(filename) : '' Nginx.rputs m.header Nginx.rputs m.md2html(markdown) Nginx.rputs m.footer
      
      





最初の行は、リクエストされたファイル、ヘッダー、URL、URIなどを含むすべての必要な情報を含むリクエストオブジェクトのインスタンスです。

次の行は、ディスカウントクラスのインスタンスを作成し、スタイルファイルとページタイトルを示します。

このコードは404エラーを処理しないため、ファイルがなくても、常に200の戻りコードがあります。

今、私たちはすべてを接続します

  location ~ \.md$ { add_header Content-Type text/html; mruby_content_handler "/opt/app/parse_md.rb" cache; }
      
      





結果:

mruby.ajieks.ru/sinatra

mruby.ajieks.ru/sinatra/README.ru.md



Rubyファイル


最初は、ナンバリングだけでなく、一度作成したコードhttps://github.com/fuCtor/chalksを使用してコードに色を付けることも計画していました。 しかし、すべての適応が行われた後、彼の仕事に問題が生じました。 コードは機能しているように見えましたが、ある時点でセグメンテーションフォールトでクラッシュしました。 最初の疑いは割り当てられたメモリの不足でしたが、その消費を減らした後でも、問題は消えませんでした。 色付けに関連するコードを削除した後、すべてが機能しましたが、私が望んでいたほど美しくはありませんでした。

結果を変更する
 module CGI TABLE_FOR_ESCAPE_HTML__ = {"&"=>"&", '"'=>""", "<"=>"<", ">"=>">"} def self.escapeHTML(string) string.gsub(/[&\"<>]/) do |ch| TABLE_FOR_ESCAPE_HTML__[ch] end end end class String def ord self.bytes[0] end end class Chalk COMMENT_START_CHARS = { ruby: /#./, cpp: /\/\*|\/\//, c: /\/\// } COMMENT_END_CHARS = { cpp: /\*\/|.\n/, ruby: /.\n/, c: /.\n/, } STRING_SEP = %w(' ") SEPARATORS = " @(){}[],.:;\"\'`<>=+-*/\t\n\\?|&#" SEPARATORS_RX = /[@\(\)\{\}\[\],\.\:;"'`\<\>=\+\-\*\/\t\n\\\?\|\&#]/ def initialize(file) @filename = file @file = File.new(file) @rnd = Random.new(file.hash) @tokens = {} reset end def parse &block reset() @file.read.each_char do |char| @last_couple = ((@last_couple.size < 2) ? @last_couple : @last_couple[1]) + char case(@state) when :source if start_comment?(@last_couple) @state = :comment elsif STRING_SEP.include?(char) @string_started_with = char @state = :string else process_entity(&block) if (@entity.length == 1 && SEPARATORS.index(@entity)) || SEPARATORS.index(char) end when :comment process_entity(:source, &block) if end_comment?(@last_couple) when :string if (STRING_SEP.include?(char) && @string_started_with == char) @entity += char process_entity(:source, &block) char = '' elsif char == '\\' @state = :escaped_char else end when :escaped_char @state = :string end @entity += char end end def to_html(&block) html = '' if block block.call( '<table><tr><td><pre>' ) else html = '<table><tr><td><pre>' end line_n = 1 @file.readlines.each do if block block.call( "<a href='#'><b>#{line_n}</b></a>\n" ) else html += "<a href='#'><b>#{line_n}</b></a>\n" end line_n += 1 end @file = File.open(@filename) if block block.call( '</pre></td><td><pre>' ) else html += '</pre></td><td><pre>' end parse do |entity, type| entity = entity.gsub("\t", ' ') if block block.call( entity ) #block.call(highlight( entity , type)) else html += entity #html += highlight( entity , type) end end if block block.call( '</pre><td></tr></table>' ) else html + '</pre><td></tr></table>' end end def language @language ||= case(@file.path.to_s.split('.').last.to_sym) when :rb :ruby when :cpp, :hpp :cpp when :c, :h :c when :py :python else @file.path.to_s.split('.').last.to_s end end private def process_entity(new_state = nil, &block) block.call @entity, @state if block @entity = '' @state = new_state if new_state end def reset @file = File.open(@filename) if @file @state = :source @string_started_with = '' @entity = '' @last_couple = '' end def color(entity) entity = entity.strip entity.gsub! SEPARATORS_RX, '' token = '' return token if entity.empty? #return token if token = @tokens[entity] return '' if entity[0].ord >= 128 rgb = [ @rnd.rand(150) + 100, @rnd.rand(150) + 100, @rnd.rand(150) + 100 ] token = String.sprintf("#%02X%02X%02X", rgb[0], rgb[1], rgb[2]) #token = "#%02X%02X%02X" % rgb #@tokens[entity] = token return token end def highlight(entity, type) esc_entity = CGI.escapeHTML( entity ) case type when :string, :comment "<span class='#{type}'>#{esc_entity}</span>" else rgb = color(entity) if rgb.empty? esc_entity else "<span rel='t#{rgb.hash}' style='color: #{rgb}' >#{esc_entity}</span>" end end end def start_comment?(char) rx = COMMENT_START_CHARS[language] char.match rx if rx end def end_comment?(char) rx = COMMENT_END_CHARS[language] char.match rx if rx end end
      
      







そして実際には、ファイルの読み取りと番号付けを実行するコード:

 r = Nginx::Request.new Nginx.rputs '<html><link rel="stylesheet" href="/st/code.css" type="text/css" /><body>' begin ch = Chalk.new(r.filename) data = ch.to_html Nginx.rputs data rescue => e Nginx.rputs e.message end Nginx.rputs '</body></html>'
      
      





すべてを接続します。 なぜなら Chalkクラスは常に使用されているため、事前にロードします。

mruby_init '/opt/app/init.rb';

この行は、設定のサーバーセクションの前に追加されます。 次に、ハンドラーをすでに示しています。

  location ~ \.rb$ { add_header Content-Type text/html; mruby_content_handler "/opt/app/parse_code.rb" cache; }
      
      





これで、結果を見ることができます: mruby.ajieks.ru/sinatra/lib/sinatra/main.rb



おわりに



したがって、別の言語を使用して、高度なクエリ処理、フィルタリング、キャッシングを実装できます。 このモジュールが戦闘条件で使用する準備ができているかどうかはわかりません。 テスト中、サーバー全体がフリーズしましたが、手の曲がりが発生する可能性があります。または、すべてが完全に開発されているわけではありません。 プロジェクトの開発をフォローします。



興味のある方は、上記のリンクを使用して、記事で指定されているパフォーマンススクリプトを実行できます。

サーバーは、最も単純なマシンであるUbuntu 12.04 x64のDigitalOceanにデプロイされます。 プロセス数2、接続1024。追加の設定は行われませんでした。 サーバーがハングした場合、10分ごとにnginxを再起動します。



All Articles