[Rails5] Rails5から外部htmlを読み込む方法

# Rails5から外部HTMLを読み込む方法

既にHTMLでページが出来上がっているけど、
その完成した静的HTMLをRails5から出力する方法を考えてみました。

これが出来れば静的なHTMLなどに対してアクセスした際の追加処理をRails5上で加える事が可能です。

# やり方概要

1. routesファイルで全てのパスへのアクセスを1つのアクションにまとめる。
2. action上ではファイルの存在チェックやHTTPレスポンスを返す。

これだけでRails5上から静的HTMLを返す事が可能です。
実際に具体的な処理を見ていきます。

# 事前準備

Railsアプリケーションルートディレクトリ配下にprivateディレクトリを作成し、その中に下記ファイルを格納します。
通常静的ページはRailsアプリケーションルート/publicディレクトリにHTMLを格納しますが、
今回はpublicディレクトリ配下ではなく別ディレクトリに格納した前提で進めていきます。

* a.html
* a.css
* a.js
* a.png

それぞれのファイルの内容を見ていきます。

``` a.html
<!DOCTYPE html>
<html>
  <head>
    <title>title</title>
    <meta charset="utf-8">
    <script type="text/javascript" src="a.js" charset="UTF-8" ></script>
    <link rel="stylesheet" type="text/css" href="a.css"></link>
  </head>
  <body>
    <h1>abcddefghlj</h1>
    <img src="a.png" />
  </body>
</html>
```

``` a.js
window.onload = function() { window.alert('aaaa') }
```

``` a.css
h1 {
  border-bottom: solid 2px red;
}
```

a.pngは好きなpngファイルを格納してください。

# Routesファイルを一つのアクションを参照するように修正

下のコードのように処理を修正します。
具体的にはルートディレクトリやその他全体のパスに対して、
`static#show` を動作させるようにしています。

``` config/routes.rb
Rails.application.routes.draw do
  root 'static#show'
  get '*path', to: 'static#show'
end
```

上記は [こちら](https://stackoverflow.com/questions/35024923/rails-4-match-all-routes-for-one-controller)を参考にしました。


# 静的HTMLを返すアクションの作成

``` app/controllers/static_controller.rb
class StaticController < ApplicationController
  def show
    # request pathを取得
    private_root_path = "#{Rails.root}/private/"
    file_path = File.expand_path( File.join( "#{private_root_path}" , "#{request.path_info}" ) )

    # railsルート以外のディレクトリやファイルが存在しない場合
    if !file_path.start_with?( "#{private_root_path}" ) || !File.exist?(file_path) || File.directory?(file_path)
      render file: "#{Rails.root}/public/404.html", layout: false, status: 404, content_type: 'text/html'
      return
    end

    # ファイルが存在する場合
    respond_to do |format|
      format.js { send_data File.read(file_path), :type => "application/javascript" , :disposition => "inline" }
      format.css { send_data File.read(file_path), :type => "text/css" }
      format.png { send_data File.read(file_path), :type => "image/png", :disposition => "inline" }
      format.json { render json:"json" }
      format.html { 
        # render html: File.read(path).html_safe 
        render file: "#{file_path}", layout: false, status: 200
      }
    end
  end
end
```

このコードで少なくとも js,css,png,htmlはブラウザ上に返すことが出来ます。

以上が静的HTMLを返すRailsのサンプルです。

# 実際に試してみた。

![rails_static.png](https://qiita-image-store.s3.amazonaws.com/0/383232/2c69ef9b-2486-c5e2-d3e5-bc4561bb71c3.png)

htmlの表示やJavascriptの動作などが行えている事が確認できます。


# おまけ

## アクションを追加したい場合

上で記載したコードだと全てのパスに対して1つのアクションが呼び出されますので、特定URLにアクセスしたい場合のアクションを追加する方法を記載しておきます。

具体的には下記コードの追加です。

``` config/routes.rb
Rails.application.routes.draw do
  get '/special', to: 'static#special' # add line
  root 'static#show'
  get '*path', to: 'static#show'
end
```

``` app/controllers/static_controller.rb
  # 下記アクションをStaticController内に追加
  def special
    render html: "special html!!"
  end
```

これで URL/specialというようにアクセスした場合は、
StaticController#Specialというアクションが実行されます。

## [CVE-2019-5418](https://groups.google.com/forum/#!topic/rubyonrails-security/pFRKI96Sm8Q) 問題

どうも render fileを使用した場合に発生するセキュリティホールがあるようです。
上記コードを使用する際は各々使用しているRailsVersionを確認してください。
[こちら](https://tech.pepabo.com/2019/03/18/analysis-rails-vulnerabilities/) が参考になると思います。

# 読んでいただきありがとうございました。
Railsを学び始めたところですので、
誤りや問題点・指摘などがある記事になっているかもしれません。
もし何かありましたらご指摘お願いします。
またコメントやこうしたら良いよみたいなアドバイスがあればコメントいただけると嬉しいです。
特に上記Rubyのアクションコードを記載していますが、行数が長くもう少し短くしたいです。


# 参考URL

* https://stackoverflow.com/questions/35024923/rails-4-match-all-routes-for-one-controller
* https://groups.google.com/forum/#!topic/rubyonrails-security/pFRKI96Sm8Q
* https://tech.pepabo.com/2019/03/18/analysis-rails-vulnerabilities/