Madogiwa Blog

主に技術系の学習メモに使っていきます。

Rubyで作るオレオレWebフレームワーク「第3回 クエリ文字列を含んだGETに対応したRouting機能作る🛣」

RubyでオレオレWebフレームワークを作っているのですが、だいぶ形になってきたので知識の整理がてら色々まとめてみる✍
今回は第3回目です🤟

👇第2回 GETに対応したRouting機能を作る🛣はこちら https://madogiwa0124.hatenablog.com/entry/2019/09/01/233842

今回のゴール

前回は、GETリクエストに対応したRouting機能を作りましたが、それをクエリ文字列を受け取れるように修正します👩‍💻 下記のように定義したら/hello?word="hoge"にアクセス時に'Hello hoge.'が表示出来るような機能を作ります。

require_relative './lib/main.rb'

router.get '/hello' do |params|
  "Hello #{params['word']}."
end

太字で記載したようにリクエスト時にクエリ文字列を追加で取得し、routeで定義したProcオブジェクトを実行する際に引数として渡します📦

  • WEBrickからリクエスト(URL、METHOD、クエリ文字列)取得
  • appmainで定義したDSLを使って作成したrouterで管理しているroutingをリクエストのURLとMETHODから探索
  • 見つかったroutingのProcオブジェクトを返却
  • applicationでProcオブジェクトの引数にクエリ文字列を渡して実行
  • 結果からレスポンスを生成
  • WEBrickにレスポンスを返却

まずはリクエスト時にクエリ文字列を取得する。

まずはリクエストからURLに設定されたクエリ文字列を取得していきます。
application.rbでそのまま実装してもいいのですが、処理が結構多くなってきたのでRack::Requestを継承した独自クラスを作ってそちらに実装していきます👩‍💻

👇実装はこんな感じ、envを引数でとってurlmethodqueryを設定する処理とクエリ文字列で渡された値を返却するparamsメソッドを追加しています🙋‍

require 'rack'

class MyRequest < Rack::Request
  def initialize(env)
    super
    @url = env['REQUEST_URI']
    @method = env['REQUEST_METHOD']
    @query = URI.parse(url).query
  end

  def params
    return unless query
    @params ||= Hash[URI.decode_www_form(query)]
  end

  attr_reader :url, :method, :query
end

rubyの標準ライブラリのURIを使うとURI.parse(url).queryでクエリ文字列部分が取得出来て、URI.decode_www_formの結果をHashの引数で渡すと簡単にHash形式で取得出来るので便利ですね✨

docs.ruby-lang.org

👇実際にapplication.rbに組み込んだ形がこちら

require 'my_request.rb'
require 'rack'

class Application

  def initialize(router:)
    @router = router
  end

  attr_reader :router, :request

  def call(env)
    @request = MyRequest.new(env)
    body = router.bind(url: request.url, method: request.method).process.call
    ['200', {'Content-Type' => 'text/html'}, [body]]
  end

  def run!
    Rack::Handler::WEBrick.run self
  end
end

RoutingのProcオブジェクトにクエリ文字列を渡す

リクエストに設定されたクエリ文字列を取得出来るようになったので、今度はそれをroutingで定義したProcオブジェクトに渡していきます🙋‍
これは簡単で単純にcallの引数にrequest.paramsを渡すだけです👍

require 'my_request.rb'
require 'rack'

class Application

  def initialize(router:)
    @request = MyRequest.new(env)
    @router = router
  end

  attr_reader :router, :request

  def call(env)
    body = router.bind(url: request.url, method: request.method).process.call(request.params)
    ['200', {'Content-Type' => 'text/html'}, [body]]
  end

  def run!
    Rack::Handler::WEBrick.run self
  end
end

これで下記のようなroutingを定義してhttp://localhost:8080/hello?word=hogeにアクセスすると、、、

router.get '/hello' do |params|
  "Hello #{params['word']}"
end

f:id:madogiwa0124:20190908213221p:plain

無事にクエリ文字列が反映されました🙌

おわりに

今回はクエリ文字列を含んだGETに対応したRouting機能を作りました👩‍💻
ここまで出来ると文字列のみを返すような静的サイトだったらなんとか実現できるぐらいには出来てきましたね👍

次回はViewのレンダリング処理を実装していこうかなと思います 🙋‍♀️ レンダリングが出来るとちょっとフレームワークっぽくなってきた感じがしますね✨