Rubyで独自のWebサーバーを構築する
Rubyを使用して独自のWebサーバーを構築したことがありますか?
すでに次のような多くのサーバーがあります:
- プーマ
- 薄い
- ユニコーン
しかし、これはすばらしい学習演習だと思います 単純なWebサーバーがどのように機能するかを知りたい場合。
この記事では、これを行う方法を学習します。
ステップバイステップ!
ステップ1:接続をリッスンする
どこから始めますか?
最初に必要なのは、TCPポート80で新しい接続をリッスンすることです。
Rubyでのネットワークプログラミングについての投稿をすでに書いたので、ここではそれがどのように機能するかを説明しません。
コードをお渡しします :
require 'socket' server = TCPServer.new('localhost', 80) loop { client = server.accept request = client.readpartial(2048) puts request }
このコードを実行すると、ポート80で接続を受け入れるサーバーができます。まだそれほど多くはありませんが、着信リクエストがどのように見えるかを確認できます。
注 :Linux / Macシステムでポート80を使用するには、root権限が必要です。別の方法として、1024を超える別のポートを使用できます。8080が好きです🙂
リクエストを生成する簡単な方法は、ブラウザまたはcurl
のようなものを使用することです。 。
これを行うと、サーバーにこれが印刷されます:
GET / HTTP/1.1 Host: localhost User-Agent: curl/7.49.1 Accept: */*
これはHTTPリクエストです。 HTTPは、WebブラウザとWebサーバー間の通信に使用されるプレーンテキストプロトコルです。
公式のプロトコル仕様は、https://tools.ietf.org/html/rfc7230にあります。
ステップ2:リクエストの解析
次に、リクエストをサーバーが理解できる小さなコンポーネントに分割する必要があります。
これを行うには、独自のパーサーを作成するか、既存のパーサーを使用します。独自に作成するので、リクエストのさまざまな部分が何を意味するのかを理解する必要があります。
この画像が役立つはずです :
GETリクエスト
ヘッダーは、ブラウザのキャッシュ、仮想ホスティング、データ圧縮などに使用されますが、基本的な実装では、ヘッダーを無視しても機能するサーバーを使用できます。
単純なHTTPパーサーを構築するために、リクエストデータが新しい行(\r\n
)で区切られているという事実を利用できます。 )。物事を単純にするために、エラーや妥当性のチェックは行いません。
これが私が思いついたコードです:
def parse(request) method, path, version = request.lines[0].split { path: path, method: method, headers: parse_headers(request) } end def parse_headers(request) headers = {} request.lines[1..-1].each do |line| return headers if line == "\r\n" header, value = line.split header = normalize(header) headers[header] = value end def normalize(header) header.gsub(":", "").downcase.to_sym end end
これにより、解析されたリクエストデータを含むハッシュが返されます。使用可能な形式でリクエストを受け取ったので、クライアントへの応答を作成できます。
ステップ3:応答の準備と送信
応答を作成するには、要求されたリソースが利用可能かどうかを確認する必要があります。つまり、ファイルが存在するかどうかを確認する必要があります。
これを行うために私が書いたコードは次のとおりです。
SERVER_ROOT = "/tmp/web-server/" def prepare_response(request) if request.fetch(:path) == "/" respond_with(SERVER_ROOT + "index.html") else respond_with(SERVER_ROOT + request.fetch(:path)) end end def respond_with(path) if File.exists?(path) send_ok_response(File.binread(path)) else send_file_not_found end end
ここで起こっていることが2つあります :
- まず、パスが
/
に設定されている場合 必要なファイルはindex.html
であると想定しています。 。 - 次に、要求されたファイルが見つかった場合、OK応答でファイルの内容を送信します。
ただし、ファイルが見つからない場合は、通常の404 Not Found
を送信します。 応答。
最も一般的なHTTP応答コードの表
参考までに。
200 | OK |
301 | |
302 | |
304 | |
400 | |
401 | |
403 | |
404 | |
500 | |
502 |
応答クラスとメソッド
最後の例で使用されている「送信」メソッドは次のとおりです。
def send_ok_response(data) Response.new(code: 200, data: data) end def send_file_not_found Response.new(code: 404) end
そして、これがResponse
です クラス:
class Response attr_reader :code def initialize(code:, data: "") @response = "HTTP/1.1 #{code}\r\n" + "Content-Length: #{data.size}\r\n" + "\r\n" + "#{data}\r\n" @code = code end def send(client) client.write(@response) end end
応答は、テンプレートといくつかの文字列補間から作成されます。
この時点で、接続を受け入れるloop
ですべてを結び付ける必要があります。 次に、機能するサーバーが必要です。
loop { client = server.accept request = client.readpartial(2048) request = RequestParser.new.parse(request) response = ResponsePreparer.new.prepare(request) puts "#{client.peeraddr[3]} #{request.fetch(:path)} - #{response.code}" response.send(client) client.close }
SERVER_ROOT
の下にいくつかのHTMLファイルを追加してみてください ディレクトリとあなたはあなたのブラウザからそれらをロードすることができるはずです。これは、画像を含む他の静的アセットにも役立ちます。
もちろん、実際のWebサーバーには、ここでは取り上げなかった多くの機能があります。
これがいくつかのリストです 不足している機能の一部であるため、演習として自分で実装できます(練習はスキルの母です!):
- 仮想ホスティング
- MIMEタイプ
- データ圧縮
- アクセス制御
- マルチスレッド
- 検証をリクエスト
- クエリ文字列の解析
- POSTボディ解析
- ブラウザのキャッシュ(応答コード304)
- リダイレクト
セキュリティに関するレッスン
ユーザーからの入力を受け取り、それを使って何かをすることは常に危険です。私たちの小さなウェブサーバープロジェクトでは、ユーザー入力はHTTPリクエストです。
「パストラバーサル」と呼ばれる小さな脆弱性を導入しました。 SERVER_ROOT
の外部にいる場合でも、ユーザーはWebサーバーユーザーがアクセスできるすべてのファイルを読み取ることができます。 ディレクトリ。
これがこの問題の原因です:
File.binread(path)
この問題を自分で悪用して、実際の動作を確認することができます。ほとんどのHTTPクライアント(curl
を含む)は「手動」のHTTPリクエストを作成する必要があります )URLを前処理し、脆弱性を引き起こす部分を削除します。
使用できるツールの1つはnetcatと呼ばれます。
考えられるエクスプロイトは次のとおりです。
$ nc localhost 8080 GET ../../etc/passwd HTTP/1.1
これにより、/etc/passwd
の内容が返されます Unixベースのシステムを使用している場合はファイル。これが機能する理由は、二重ドット(..
)1つのディレクトリを上に移動できるため、SERVER_ROOT
を「エスケープ」しています。 ディレクトリ。
考えられる解決策の1つは、複数のドットを1つに「圧縮」することです。
path.gsub!(/\.+/, ".")
セキュリティについて考えるときは、常に「ハッカーの帽子」をかぶって、ソリューションを破る方法を見つけようとします。たとえば、path.gsub!("..", ".")
を実行した場合 、トリプルドット(...
)を使用してそれをバイパスできます 。
完成した作業コード
この投稿のいたるところにコードがあることはわかっているので、完成した実用的なコードを探しているなら…
リンクはこちら :
https://gist.github.com/matugm/efe0a1c4fc53310f7ac93dcd1f041f6c#file-web-server-rb
お楽しみください!
概要
この投稿では、新しい接続をリッスンする方法、HTTPリクエストがどのように見えるか、そしてそれを解析する方法を学びました。また、応答コードと必要なファイルの内容(利用可能な場合)を使用して応答を作成する方法も学習しました。
そして最後に、「パストラバーサル」の脆弱性とそれを回避する方法について学びました。
この投稿を楽しんで、何か新しいことを学んだことを願っています!下のフォームで私のニュースレターを購読することを忘れないでください。そうすれば、1つの投稿を見逃すことはありません🙂
-
Rubyでパーサーを構築する方法
構文解析は、一連の文字列を理解し、それらを理解できるものに変換する技術です。正規表現を使用することもできますが、必ずしもその仕事に適しているとは限りません。 たとえば、HTMLを正規表現で解析することはおそらく良い考えではないことは一般的な知識です。 Rubyには、この作業を実行できるnokogiriがありますが、独自のパーサーを作成することで多くのことを学ぶことができます。始めましょう! Rubyでの解析 パーサーの中核はStringScannerです クラス。 このクラスは、文字列のコピーと位置ポインタを保持します。ポインタを使用すると、特定のトークンを検索するために文字列をトラバ
-
火曜日のヒント:Plex Server で独自の Spotify を作成する
Apple が Lala を買収し、殺して以来、私は Spotify やその他のストリーミング サービスを使用してギャップを埋めてきました。しかし、どこでもストリーミングできない (エンコードが不十分な YouTube のバージョンを数えない限り) さまざまなデバイスで聴きたい音楽がたくさんあります。 Plex に入ります。 Plex は、Mac またはネットワーク接続ストレージ (NAS) デバイスをサーバーとしてセットアップし、自宅から離れていても自分の音楽を iPhone にストリーミングできるようにするソフトウェア パッケージです。 Plex を設定するには、ネットワークに関する基本