「RubyとSinatraではじめるwebアプリケーション 開発の教科書」Sinatraのバージョンに注意しましょう

# はじめに
Qiita初投稿です。
この記事は2023年4月1日時点のものです。
（[追記：2023年8月29日あり](https://qiita.com/motohiro_mm/items/be1ae1153db540395712#%E8%BF%BD%E8%A8%982023%E5%B9%B48%E6%9C%8829%E6%97%A5)）

書籍「[RubyとSinatraではじめる　Webアプリケーション開発の教科書](https://www.morikita.co.jp/books/mid/085561)」で勉強しています。
手順通りにやっていて、エラーが出て苦戦しました。
そのエラーの原因について書いていこうと思います。

# 結論
先に結論を言うと、
**Sinatra2系でやりましょう！**
ということです。

# 作業環境
- Ruby　3.1.2
- Sinatra 3.0.5

# 苦戦した所
### 7.3.2 SinatraでCookieを手軽に扱う (151ページ)
sessiontest.rbを作成します。
```ruby:sessiontest.rb
require 'sinatra'

set :sessions,
  secret: "xxx"

set :environment, :production

get '/page1' do
  session[:message] = 'ABC'
  redirect '/page2'
end

get '/page2' do
  session[:message]
end
```
実行します。
```
$　bundle exec ruby sessiontest.rb
　　　　　　　　　　　　　　SECURITY WARNING: Your secret is not long enough. It must be at least
　　　　　　　　　　　　　　32 bytes long and securely random. To generate such a key for use
　　　　　　　　　　　　　　you can run the following command:

　　　　　　　　　　　　　　ruby -rsecurerandom -e 'p SecureRandom.hex(32)'

　　　　　　　　　　　　　　Called from: /Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/builder.rb:158:in `new'.
[2023-04-07 14:26:01] INFO  WEBrick 1.8.1
[2023-04-07 14:26:01] INFO  ruby 3.1.2 (2022-04-12) [x86_64-darwin22]
== Sinatra (v3.0.4) has taken the stage on 4567 for production with backup from WEBrick
[2023-04-07 14:26:01] INFO  WEBrick::HTTPServer#start: pid=2940 port=4567
```
警告が出たものの、Sinatraは動いたようです。
書籍に従って`/page1`に繋いでみるもエラーが出ました。
```
[2023-04-07 14:23:27] ERROR ArgumentError: key must be 32 bytes
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-protection-3.0.5/lib/rack/protection/encryptor.rb:24:in `key='
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-protection-3.0.5/lib/rack/protection/encryptor.rb:24:in `encrypt_message'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-protection-3.0.5/lib/rack/protection/encrypted_cookie.rb:241:in `write_session'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/session/abstract/id.rb:388:in `commit_session'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/session/abstract/id.rb:268:in `context'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/session/abstract/id.rb:260:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/logger.rb:17:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/common_logger.rb:38:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:261:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:254:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/head.rb:12:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/method_override.rb:24:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:219:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:2007:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:1566:in `block in call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:1782:in `synchronize'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:1566:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/handler/webrick.rb:95:in `service'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:140:in `service'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:96:in `run'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:310:in `block in start_thread'
127.0.0.1 - - [07/Apr/2023:14:23:26 JST] "GET /page1 HTTP/1.1" 500 300
- -> /page1
[2023-04-07 14:23:27] ERROR ArgumentError: key must be 32 bytes
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-protection-3.0.5/lib/rack/protection/encryptor.rb:24:in `key='
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-protection-3.0.5/lib/rack/protection/encryptor.rb:24:in `encrypt_message'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-protection-3.0.5/lib/rack/protection/encrypted_cookie.rb:241:in `write_session'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/session/abstract/id.rb:388:in `commit_session'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/session/abstract/id.rb:268:in `context'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/session/abstract/id.rb:260:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/logger.rb:17:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/common_logger.rb:38:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:261:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:254:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/head.rb:12:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/method_override.rb:24:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:219:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:2007:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:1566:in `block in call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:1782:in `synchronize'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sinatra-3.0.5/lib/sinatra/base.rb:1566:in `call'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.6.4/lib/rack/handler/webrick.rb:95:in `service'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:140:in `service'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/httpserver.rb:96:in `run'
	/Users/omisan/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/webrick-1.8.1/lib/webrick/server.rb:310:in `block in start_thread'
127.0.0.1 - - [07/Apr/2023:14:23:27 JST] "GET /favicon.ico HTTP/1.1" 500 300
http://127.0.0.1:4567/page1 -> /favicon.ico
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3321813/aadeea10-c42c-e2cc-9f8e-1c36a48270c7.png)
先ほどでた警告部分を抜粋します。
```
SECURITY WARNING: Your secret is not long enough. It must be at least
32 bytes long and securely random. To generate such a key for use
you can run the following command:

ruby -rsecurerandom -e 'p SecureRandom.hex(32)'
```
「あなたのsecretは長さが不十分です」ということでした。
警告メッセージに従ってみます。
```
$ ruby -rsecurerandom -e 'p SecureRandom.hex(32)'
"4fdd9f5514e196ed36cf07e0f8e168929320516c881946687382a1fc34b76cca"
```
ここで得られた
`"4fdd9f5514e196ed36cf07e0f8e168929320516c881946687382a1fc34b76cca"`
をsecretに入れてみます。（sessiontest2.rbとします）
```ruby:sessiontest2.rb
require 'sinatra'

set :sessions,
  secret: "4fdd9f5514e196ed36cf07e0f8e168929320516c881946687382a1fc34b76cca"

  set :environment, :production

get '/page1' do
  session[:message] = 'ABC'
  redirect '/page2'
end

get '/page2' do
  session[:message]
end
```
すると警告もなくSinatraが起動され、エラーも一応起こりませんでした。
```
$ bundle exec ruby sessiontest2.rb
[2023-04-08 17:17:31] INFO  WEBrick 1.8.1
[2023-04-08 17:17:31] INFO  ruby 3.1.2 (2022-04-12) [x86_64-darwin22]
== Sinatra (v3.0.4) has taken the stage on 4567 for production with backup from WEBrick
[2023-04-08 17:17:31] INFO  WEBrick::HTTPServer#start: pid=7529 port=4567
127.0.0.1 - - [08/Apr/2023:17:17:46 +0900] "GET /page1 HTTP/1.1" 302 - 0.0338
127.0.0.1 - - [08/Apr/2023:17:17:46 JST] "GET /page1 HTTP/1.1" 302 0
- -> /page1
127.0.0.1 - - [08/Apr/2023:17:17:46 +0900] "GET /page2 HTTP/1.1" 200 3 0.0018
127.0.0.1 - - [08/Apr/2023:17:17:46 JST] "GET /page2 HTTP/1.1" 200 3
- -> /page2
127.0.0.1 - - [08/Apr/2023:17:17:46 +0900] "GET /favicon.ico HTTP/1.1" 404 469 0.0025
127.0.0.1 - - [08/Apr/2023:17:17:46 JST] "GET /favicon.ico HTTP/1.1" 404 469
http://127.0.0.1:4567/page2 -> /favicon.ico
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3321813/63280ec6-904e-09c0-dc3c-958045983503.png)

次に進んでみます。

### 7.4 Cookieを中身から理解する (152ページ)
書籍に従い得られたCookieを貼り付けてdecodecookie.rbを作成します。
"xxx"部分も先ほどのsecretに入れた文字列に変更します。
```ruby:decodecookie.rb
require 'cgi'
require 'base64'
require 'openssl'
require 'sinatra'

s = "9R7173F0gEBcTVOLxxlWaJ8yGKWeQnPTi8bTHXjTdcYCOdW1l9uEbA%2Fl%2F4ER7DxgAXGVSCqLCXNwn8pPTH1ij%2B826OnN15wLrgiT3s1fvTxmmXbjafGUxKECWLow%2FNBqDZAACEhWtxHvPt8uBde1iViPXQ9K8xbtQ2o0jBUmun1xK20yEk1l6kGI325jiTU%2BnbeVJMmAvkMHxTRpG25lP679e9A7fC3YJIybd1q9FC16ogShvPoKHW06dPj7cQpe1slNoeEkxwb3YdZ1VT6ldrH95y7EbS4JeCXCIqUtM1%2FAz6JX1mHABz9BiKpYlImgDXkVNDLWDdfvVMkmTRGMu4un%2FAO0FUyDDQ7IQOpwGQL4P9rJzhCsUMCB3C%2BoSh8C2aB21f4eJbr1Y%2FOSxU31Dg%3D%3D--p2WeR3WUg8yCN5Bq--RPUJtU%2BxylAQ6ORwZ0m4Gw%3D%3D"

sb64, digest = CGI.unescape(s).split("--")
puts Marshal.load(Base64.decode64(sb64))
puts OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, "4fdd9f5514e196ed36cf07e0f8e168929320516c881946687382a1fc34b76cca", sb64)
puts digest
```
実行すると、エラーになりました。
```
$ bundle exec ruby decodecookie.rb               
<internal:marshal>:34:in `load': incompatible marshal file format (can't be read) (TypeError)
	format version 4.8 required; 245.30 given
	from decodecookie.rb:9:in `<main>'
```
ここで苦戦してしまいました。
暗号化されたcookieの復元方法はわかりませんでした。

# 警告・エラーの原因
最初の警告・エラーの原因は、**Sinatra 3.0になってcookieが暗号化されるようになった**からと思われます。
下記の2番目のところです。

https://github.com/sinatra/sinatra/blob/main/CHANGELOG.md#300--2022-09-26

詳細は以下になります。

https://github.com/sinatra/sinatra/pull/1324/files#diff-d91dacd5fa5ae6f9bf2a5597340fdc531421266ba821aa7a5739b941e5677fa9R1775

# 解決策
暗号化されたcookieの復元方法は分からなかったので、
本書を読み進める場合はSinatraのバージョンを2系に固定したほうが良いかと思います。

### Sinatraのバージョンを2系でインストールする方法
Gemfileに以下を記載しインストールします。
```ruby:Gemfile
gem "sinatra", "~> 2.2.3"
gem "webrick"
```

### Sinatra2系にした実行結果
sessiontest.rbを実行し、警告・エラーなくCookieを取り出すことができました。
```
$ bundle exec ruby sessiontest.rb 
[2023-04-08 17:50:31] INFO  WEBrick 1.8.1
[2023-04-08 17:50:31] INFO  ruby 3.1.2 (2022-04-12) [x86_64-darwin22]
== Sinatra (v2.2.3) has taken the stage on 4567 for production with backup from WEBrick
[2023-04-08 17:50:31] INFO  WEBrick::HTTPServer#start: pid=8355 port=4567
127.0.0.1 - - [08/Apr/2023:17:50:47 +0900] "GET /page1 HTTP/1.1" 302 - 0.0116
127.0.0.1 - - [08/Apr/2023:17:50:47 JST] "GET /page1 HTTP/1.1" 302 0
- -> /page1
127.0.0.1 - - [08/Apr/2023:17:50:47 +0900] "GET /page2 HTTP/1.1" 200 3 0.0018
127.0.0.1 - - [08/Apr/2023:17:50:47 JST] "GET /page2 HTTP/1.1" 200 3
- -> /page2
```
書籍に従いdecodecookie.rbを実行すると、書籍通りの結果を得ることができました。
```
$ bundle exec ruby decodecookie.rb 
{"session_id"=>"f4cd87f8f2893dab436864d4087907821e7600730a71dadc9d02c81f32e84509", "csrf"=>"jeylTPdKJ5bO0UpB8qYurYYHmqqfKjDhmYCCYecevWs=", "tracking"=>{"HTTP_USER_AGENT"=>"ec8d6f2f19615b997197e0af82095e2dbbcbfb10"}, "message"=>"ABC"}
27bc3e7ffefc30481237f5a605235382f04e9f2a
27bc3e7ffefc30481237f5a605235382f04e9f2a
[2023-04-08 17:52:51] INFO  WEBrick 1.8.1
[2023-04-08 17:52:51] INFO  ruby 3.1.2 (2022-04-12) [x86_64-darwin22]
== Sinatra (v2.2.3) has taken the stage on 4567 for development with backup from WEBrick
[2023-04-08 17:52:51] INFO  WEBrick::HTTPServer#start: pid=8432 port=4567
```

# 書籍のSinatraのバージョンについて
本書では、bundlerでSinatraをインストールしておりGemfileは以下のように記載しています。
```ruby:Gemfile
gem "sinatra"
gem "webrick"
```
Gemfile.lockの内容は書籍に記載がなく、バージョンは分かりませんでした。
推測ですが、書籍の発行が2021年7月30日となっていたので、
書籍のSinatraは2系であったのではないかと思われます。

## 追記　2023年8月29日
[本書のページ](https://www.morikita.co.jp/books/mid/085561)の関連サイトに、[著者によるサポートページ](http://teacher.nagano-nct.ac.jp/ito/Sinatrabook/)がありました！
そちらに以下の記載がありました。
>本書ではSinatraのバージョン2系を使っています。

Sinatra２系に変更するやり方、またSinatra３系で同じ結果を得るやリ方も記載がありましたので、ぜひ確認してみてください！

# おわりに
最初からSinatra2系でインストールしたほうが、書籍と同様の結果を得られます。
その方が理解を深めやすく学びやすいかと思います。
バージョンを気にせずインストールすると、現時点ではSinatra3系になりますので、ご注意ください。

最後に、たくさんご指導いただきました@jnchitoさん、本当にありがとうございました！