RubyでHTTPサーバを立てる②
昨日の投稿(https://be-kan.hatenablog.jp/entry/2018/05/24/012445)の続きになります。
元記事はこちら
blog.appsignal.com
内容的には
・HTTPサーバの上でRails Appを動かす
のみです。
当内容、GitHubにソースコードをあげています。
https://github.com/be-kan/http_server_in_ruby
HTTPサーバの上でRaila Appを動かす
使用するRails Appはこちらになります。
https://github.com/jeffkreeftmeijer/wups.git
ただデータをpostしその一覧を取得できるだけのものです。
もちろん自分で用意していただいても構いません。
http_server.rbがあるディレクトリで、Rails Appをgit submoduleで管理します。
$ ls http_server.rb $ git submodule add https://github.com/jeffkreeftmeijer/wups.git sample_blog
前回、http_server.rbは以下のコードで終了していると思います。
require 'socket' require 'rack' require 'rack/lobster' app = Rack::Lobster.new server = TCPServer.new 5678 while session = server.accept request = session.gets puts request method, full_path = request.split(' ') path, query = full_path.split('?') status, headers, body = app.call({ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query }) session.print "HTTP/1.1 #{status}\r\n" headers.each do |key, value| session.print "#{key}: #{value}\r\n" end session.print "\r\n" body.each do |part| session.print part end session.close end
Rack::Lobster Appを動かしていましたが、これをRails Appに変更します。
require 'socket' require_relative 'sample_blog/config/environment' app = Rails.application server = TCPServer.new 5678
これでサーバを立ててアクセスしてみると、以下のようなエラーがきます。
500 Internal Server Error If you are the administrator of this website, then please read this web application's log file and/or the web server's log file to find out what went wrong.
言われた通りにlogを確認すると、
Error during failsafe response: Missing rack.input
とのことです。
Rack Appを動かすには、REQUEST_METHOD, PATH_INFO, QUERY_STRINGだけで十分だったのですが、Rails Appを動かすにはそれだけでは不十分のようです。
Rackには、Rack::Lintという、RackがRailsに渡すべき変数を用意してくれるものがあるので、
Rails.applicationをRack::Lintでラッピングします。
require 'socket' require_relative 'sample_blog/config/environment' app = Rack::Lint.new(Rails.application) server = TCPServer.new 5678
これでもう一度サーバを立ててlocalhost:5678にアクセスすると、サーバがクラッシュしました。
~path/to/rack/lint.rb:20:in `assert': env missing required key SERVER_NAME (Rack::Lint::LintError)
Rack::LintはRailsに十分に変数を渡しますが、そもそもRack::Lintに必要な変数を渡せていないようです。
エラーで表示された変数を追加して行くと、最終的に以下のようになります。
# ... method, full_path = request.split(' ') path, query = full_path.split('?') input = StringIO.new input.set_encoding 'ASCII-8BIT' status, headers, body = app.call({ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query || '', 'SERVER_NAME' => 'localhost', 'SERVER_PORT' => '5678', 'rack.version' => [1,3], 'rack.input' => input, 'rack.errors' => $stderr, 'rack.multithread' => false, 'rack.multiprocess' => false, 'rack.run_once' => false, 'rack.url_scheme' => 'http' })
すると、以下のようなメッセージがブラウザに表示されます。
エラーとはいえ、ついにRails側のエラーが来ましたね!
ログを確認すると、
IPAddr::InvalidAddressError: invalid address
というエラーです。
クライアントのIPアドレスを指定しなければなりません。
今はlocalhostで動かしているので、以下のように設定します。
status, headers, body = app.call({ # ... 'SERVER_PORT' => '5678', 'REMOTE_ADDR' => '127.0.0.1', 'rack.version' => [1,3], # ... })
ついにRailsが表示されました。
ここから、Rails Appに対して、POSTを行います。
Rails App的にはすでにpostに対応しているので、あとはサーバとのつなぎこみのみです。
ちなみに、元記事にはありませんが、Rails Appのデータベースがないので、
rake db:migrate
でデータベースを作成しておきましょう。
サーバを立て、http://localhost:5678/postsにアクセスし、New Postから新規にpostを作成しようとすると
ActionController::InvalidAuthenticityToken
と言われます。
通常ならauthenticity tokenはリクエストと共に送信されるのですが、今のコードでは何もポストデータを送っていないようです。
そこで、session.getsで取得したリクエストの情報を、headerにハッシュとして受け取ります。
リクエストの大きさ(何バイトか)をあらかじめ知るためにbodyにContent-Lengthの情報を入れ、さらにrack.inputには先のbodyで初期化したString.IOインスタンスを入れます。
headerのクッキーも指定し、authenticityをクリアします。
headers = {} while (line = session.gets) != "\r\n" key, value = line.split(':', 2) headers[key] = value.strip end body = session.read(headers["Content-Length"].to_i) # ... status, headers, body = app.call({ # ... 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_COOKIE' => headers['Cookie'], 'rack.version' => [1,3], 'rack.input' => StringIO.new(body), 'rack.errors' => $stderr, # ... })
これで無事POSTもできるようになりました!