@ledsun blog

無味の味は佳境に入らざればすなわち知れず

リダイレクトレスポンス対応版 require_relativeを組み込んだruby.wasmを実際に動かして、問題を切り分ける

ruby.wasmでrequire_relativeしたときのリダイレクトレスポンスの扱い方 - @ledsun blog に書いたように、HTTPリクエストのリダイレクトレスポンスでURLが変わったら、変更後のURLをロード済みとして扱いたいです。

リダイレクトレスポンス対応版 require_relativeの実装

ruby.wasmのrequire_relativeではfetch APIを使います。 fetch APIからリダイレクト後のURLがとれば良いはずです。

Response.url - Web API | MDN には

url プロパティの値は、あらゆるリダイレクトの後に得られる最終的な URL になります。

と、あります。 これを使えば良さそうです。 次のようなコードを書きました。

response = JS.global.fetch(location.url).await

if response[:status].to_i == 200
  return false if @loaded_urls.include?(response[:url].to_s)

  code = response.text().await.to_s
  eval_code(code, location)

  @loaded_urls << response[:url].to_s
  true
else
  raise LoadError.new "cannot load such url -- #{location.url}"
end

@loaded_urlsresponse.url から取得したURLを保存しておきます。 次のfetchの response.url が、@loaded_urls に含まれていたら、Rubyスクリプトを実行せずにリターンします。 これで良さそうに思います。

実際に動かしてみる

しかし、E2Eテストでは失敗します。 失敗した結果 ruby.wasmのE2Eテストをデバッグする - @ledsun blog をしていました。 また、Response.urlの挙動を確認する - @ledsun blog で、Response.url単体では期待通りに動くことが確認できました。 ということは、ruby.wasmに組み込んでも期待通りに動きそうです。 確かめてみましょう。

次のようなサーバーを用意します。

require 'webrick'

root = File.expand_path 'public' # 静的ファイルが置かれるディレクトリ
server = WEBrick::HTTPServer.new :Port => 3000, :DocumentRoot => root

# リダイレクトの設定
server.mount_proc '/redirect.rb' do |req, res|
  res.status = 302
  res['Location'] = 'app.rb'
end

trap 'INT' do
  server.shutdown
end

server.start

このサーバーはpublicディレクトリにあるファイルを静的配信します。 さらに /redirect.rb にきたリクエストにはリダイレクトレスポンスを返し app.rb に誘導します。

publicディレクトリに、自分でビルドしたruby.wasmを実行する環境をつくる - @ledsun blogの要領で、browser.script.iife.jsruby+stdlib.wsmを用意します。

require_relativeの動作確認する - @ledsun blog の要領でrequire_relativeを使うindex.htmlを用意します。

<html>
  <script src="browser.script.iife.js"></script>
  <script type="text/ruby">
    require 'js/require_remote'

    module Kernel
      alias original_require_relative require_relative

      # The require_relative may be used in the embedded Gem.
      # First try to load from the built-in filesystem, and if that fails,
      # load from the URL.
      def require_relative(path)
        caller_path = caller_locations(1, 1).first.absolute_path || ''
        dir = File.dirname(caller_path)
        file = File.absolute_path(path, dir)

        original_require_relative(file)
      rescue LoadError
        JS::RequireRemote.instance.load(path)
      end
    end
  </script>
  <script type="text/ruby" data-eval="async">
    p require_relative 'redirect'
    p require_relative 'app'
  </script>
</html>

ただし次のようにリダイレクトを交えたrequire_relativeを実行します。

p require_relative 'redirect'
p require_relative 'app'

リダイレクト後の app.rb がロード済みになっているはずです。 その後 app.rb をロードしようとしてもスキップされて欲しいです。 つまり、開発コンソールに、最初にtrueつぎにfalseが表示されてほしいです。 サーバーを起動して動作を確認します。

ruby sever.rb

http://localhost:3000/ を開いて開発コンソールを確認します。

開発コンソールのスクリーンショット

期待通りです。 最初にtrueつぎにfalseが表示されました。

まとめ

わかったことをまとめます。

  1. response.urlはfetchを単体で動かしたときリダイレクト後のURLが取得できる
  2. response.urlをruby.wasm版 require_relative に組み込むと、期待通りに動く
  3. E2Eでは期待通りに動かない
  4. 特にresponse.urlの値がリダイレクト前のまま

以上から、今後はE2Eテストの実行環境を重点的にしらべていくと良さそうです。