Rails7 (zeitwerk)でgRPC (Protocol Buffers)を使ったらハマった
概要
Amazon.co.jp: スターティングgRPC 技術の泉シリーズ (技術の泉シリーズ(NextPublishing)) eBook : 武上 将樹: Kindleストア
こちらの書籍にあるGo(サーバ) × Rails(クライアント)のサンプルを写経していた際、zeitwerkの仕様にはまってエラーが出たので、回避方法をメモしておきます。
zeitwerkはRails6から採用されたオードロード機構ですが、Rails6ではActive Supportで実装されたものがclassicモードとして利用可能で、それであればエラーは発生しないとのことでした。
Rails7からはclassicモードが使えずzeitwerkで動かす必要があるため、下記サイトを参考にさせていただき対応しました。
アプリケーション外観
最終的なディレクトリ構成は下記の通り。
. ├── api # サーバ(Golang) │ ├── gen │ │ └── pb │ │ ├── filemeta.pb.go │ │ ├── image_upload_request.pb.go │ │ └── image_upload_response.pb.go │ ├── go.mod │ ├── go.sum │ ├── handler │ │ └── image_upload_handler.go │ └── server │ └── server.go ├── client # クライアント(Rails) │ ├── Gemfile │ ├── app │ │ ├── gen │ │ │ └── pb │ │ │ └── image │ │ │ └── upload │ │ │ ├── image_upload_request │ │ │ │ └── filemeta_pb.rb │ │ │ ├── image_upload_request_pb.rb │ │ │ ├── image_upload_request_services_pb.rb │ │ │ └── image_upload_response_pb.rb │ │ ├── models │ │ │ └── image_uploader.rb │ ├── config │ │ ├── application.rb ├── proto │ └── image │ └── upload │ ├── image_upload_request │ │ └── filemeta.proto │ ├── image_upload_request.proto │ └── image_upload_response.proto
ポイント1)1つのmessageにつき1つのprotoファイル
まずprotoファイルでスキーマを定義します。
最初は一つのprotoファイル内に3つのmessage
を記載していましたが、参考先にも記載がある通り、1protoファイルにつき一つのmessageとしました。zeitwerkによる定数読み込みでエラーになるためです。結果的に3つのprotoファイルを書き、大元のimage_upload_request.proto
で他の2つをimportして使うようにしました。
定義できたらprotoc
およびgrpc_tools_ruby_protoc
コマンドで各々GoとRubyのコードを自動生成します。
ポイント2)Rails側で読み込みパスを追加
config/application.rb
に下記の通り追加します。
config.paths.add Rails.root.join('app', 'gen', 'pb').to_s, eager_load: true
protoで定義して自動生成した定数の名前空間を正しく認識させるために必要でした。
ポイント3)カスタムInflectorを定義
Qiitaの記事にある通り、自前のInflectorを定義して利用します(記事の通りなので割愛)
なお自動生成したrubyのファイルimage_upload_request_services_pb.rb
について、ファイル名はservicers
となっているが中身のmodule名はImageUploadRequestService
となり、このままだとzeitwerkの読み込みエラーになったため、取り急ぎここのカスタムInflectorで対応しました(ファイル名末尾にpbがつく場合、かつ指定のファイル名の場合は、当該ファイル名を単数系に変換)。
以上の通り修正し、下記コマンドを実行してexpected file XXX to define constant XXX
のようなエラーが出なければOKです。
% bin/rails zeitwerk:check Hold on, I am eager loading the application. All is good!
Laravelアプリをサブディレクトリで動かした
ある要件でlaravelアプリをドメインURLのサブディレクトリで動かしたかったが、 ネットで調べてヒットした記事が、環境差異なのか、ことごとくハマらず動かなかったので、 最終的に動くようになった設定を公開しておく。
やりたいこと
- http://sample.com/laravelapp のようにサブディレクトリでlaravelアプリを動かす。
- 以降の説明ではサブディレクトリ名を「laravelapp」とする
環境
docker-composeにて以下のコンテナを作成。ちなみにホストはWindows10なので、docker for windowsを使用。
- nginx
- php-fpm
ホスト側のディレクトリ構成
myapp/ ├── docker-compose.yml ├── html │ └── laravelapp ├── public ├── index.php ├── nginx │ ├── default.conf │ └── nginx.conf └── php ├── Dockerfile ├── php.ini └── run.sh
docker-composeの設定
version: '3' services: nginx: image: nginx:alpine ports: - "19080:80" volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf - ./nginx/nginx.conf:/etc/nginx/nginx.conf - ./html:/var/www/html depends_on: - phpapp phpapp: build: ./php volumes: - ./html:/var/www/html - ./php/php.ini:/usr/local/etc/php/php.ini
- nginxのconf設定ファイルはホストで用意したものを反映させるためにvolumesに追記した。
- DocumentRootは/var/www/htmlとし、ホスト側のhtmlディレクトリと対応させた。
- またphp-fpmコンテナからもlaravelアプリが見れるようにするため、nginxのDocumentRootと同じディレクトリを見るようにした。
- 今回はphp.iniも自前のものを使いたかったので、volumesにおいてコンテナに反映させた。
nginxの設定
nginx.conf内でincludeされるdefault.confに設定を追記したが、nginxが動けばどこに書いてもいいと思う。
server { listen 80; server_name localhost; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log debug; root /var/www/html; location ~ ^/laravelapp/(.+\.(?:gif|jpg|css|png|svg)$) { alias /var/www/html/laravelapp/public/$1; break; } location ~ ^/laravelapp((/)?(.+))?$ { alias /var/www/html/laravelapp/public/; # 最後に / が必要 index index.php; # serverディレクティブ直下に書いてもOK try_files $uri $uri/ @admin; location ~ [^/]\.php(/|$) { # (.+\.php) => $fastcgi_script_name (/.*) => $fastcgi_path_info fastcgi_split_path_info ^(.+?\.php)(/.*)$; # FastCGIサーバへリクエストをプロキシする fastcgi_pass phpapp:9000; # / で終わるURIの後に追加されるファイル名を設定 fastcgi_index index.php; # 設定ファイル読み込み include fastcgi_params; # FastCGIサーバへ送られるパラメタ fastcgi_param SCRIPT_FILENAME ${document_root}index.php; fastcgi_param PATH_INFO $fastcgi_path_info; } } location @admin { rewrite /laravelapp(.*)$ /laravelapp/index.php$1 last; } }
- 重要なのは、locationディレクティブ内でaliasでパスを指定しているところ。
- 解釈が合っているか分からないが、
php-fpmの設定
Dockerfileの中身は下記の通り、ホスト側で用意したスクリプトrun.shをコンテナ内で実行する。
Dockerfile
FROM php:7.1.8-fpm ADD run.sh /tmp/run.sh RUN chmod +x /tmp/run.sh && sh /tmp/run.sh
run.sh
#/bin/bash apt-get update && apt-get install -y \ git \ zip \ unzip \ libpq-dev \ # install composer curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer # install laravel composer global require "laravel/installer=~1.1"
ちなみにlaravelアプリはphp-fpmコンテナができたらコンテナ内に入ってcreate-projectで新規作成するか、既存のソースコードをmyapp/htmlの直下に置く。
ここまできたらdockerを起動してコンテナを作る。
$ docker-compose up -d
http://localhost:19080/laravelapp/ をブラウザで開き、Laravelトップ画面が出たら成功。
ちなみに最後のスラッシュ(トレイリングスラッシュ=trailing slash)をなくしたい場合は、nginx側で別途設定が必要。
その他はまったところ
サブディレクトリでの動作に直接は関係ないが、今回のdocker構成ではまったところをメモ。
- セッションが保持されない
- => php.iniを見ると「session.auto_start = 0」であった。1に変更する。
サブディレクトリ対応はApacheでやればaliasを設定するだけですぐできたが、nginxは一筋なわではいかなかった。。
ユーザをグループに追加することで別ユーザのディレクトリに書き込めるようにした話
グループに追加することで書き込み権限を得る話
やりたいこと
appユーザに所有権があるディレクトリに対して、hogeユーザがファイル書き込みを行いたい。
=> appユーザが属するグループにhogeユーザを追加する
手順
初期状態(ユーザ確認)
# cat /etc/passwd ~ 省略 ~ app:x:500:500::/home/app:/bin/bash hoge:x:501:501::/home/hoge:/bin/bash
対象ディレクトリ作成(今回は新規作成)
# mkdir /var/test # ls -ld /var/test drwxr-xr-x 2 root root 4096 Feb 7 01:25 test
appユーザに所有権を与える
# chown -R app:app /var/test
appユーザは書き込みできることを確認
# su - app $ touch /var/test/test.log $ ll /var/test/ -rw-rw-r-- 1 app app 0 Feb 7 01:25 test.log
ここからが本題
hogeユーザは書き込みできないことを確認
# su - hoge $ touch /var/test/hoge.log touch: cannot touch `/var/test/hoge.log': Permission denied
グループの確認
# groups app app : app # groups hoge hoge : hoge
hogeユーザをappグループに追加
# usermod -aG app hoge
appグループに書き込み権限が無ければ与える
# ls -ld /var/test drwxr-xr-x 2 app app 4096 Feb 7 01:25 test # chmod -R g+wr /var/test # ls -ld /var/test drwxrwxr-x 2 app app 4096 Feb 7 01:25 test
追加確認
# groups hoge hoge : hoge app
hogeユーザが書き込めるようになったことを確認
# su - hoge $ touch /var/test/hoge.log $ ll /var/test/ -rw-rw-r-- 1 hoge hoge 0 Feb 7 01:36 hoge.log -rw-rw-r-- 1 app app 28 Feb 7 01:26 test.log
(おまけ) ファイルのグループはディレクトリのグループを継承するようにする
対象ディレクトリにSGIDビットを立てることで可能となる。
実施前
# ls -ld /var/test drwxrwxr-x 2 app app 4096 Feb 7 01:25 test
SGIDビットを立てる
# chmod 2774 /var/test
確認
# ls -ld /var/test drwxrwsr-- 2 app app 4096 Feb 7 01:37 /var/test
ファイルを作る
# su - hoge $ touch /var/test/hoge2.log $ ll /var/test/ -rw-rw-r-- 1 hoge app 0 Feb 7 01:37 hoge2.log -rw-rw-r-- 1 hoge hoge 0 Feb 7 01:36 hoge.log -rw-rw-r-- 1 app app 28 Feb 7 01:26 test.log
以上
iPhoneの画面が割れたので修復までの過程をまとめた
先日iPhoneの画面が割れてしまいました。
しかし思ったほど修理に時間が掛からなかったので、 誰かの参考になればと思い、過程をまとめておきます。
最初にまとめ
- 画面が割れてから修理済みのiPhoneを手に取るまでの時間 : 約24時間
- かかった費用 : 3672円(Apple Careサポート有の場合)
- 修理依頼したお店 : Apple正規サービスプロバイダ(ビックロ新宿東口店)
画面破壊
外出してて、駅のホームでうっかり落とす。
近くのauショップへ
- キャリアがauだったので、とりあえずauへ。
- Apple Careのサポートがあることを確認。
- しかし、auショップではiPhoneの修理は受けられないので、 Appleストアか正規のサービスプロバイダへ行くように言われる。
新宿のビックロへ
- 新宿にはAppleストアもあるが混んでそうだったので。
- 予約がいっぱいなので今日の受付は無理と言われる
- 翌日10時に当日受付の予約枠が解放されるので、ネットで予約してから来るのがおすすめ、と言われる。
予約状況の確認
- Appleサポートのアプリ、またはAppleサポートページで検索
- どの店舗も1週間以上予約枠が空いておらず絶望
Apple以外でiPhone修理をしてくれる店を探す
- 幸いたくさん見つかるが、料金は少々高め
- Apple以外でiPhone端末を開けられると、Apple Careの期間内であっても、もうAppleのサポートは受けられなくなるのが気になる。。
- でも今までサポートを受けたこともないし、残りのサポート期間が過ぎて更新しなければればどのみちサポートは受けられなくなるし、、
- 何よりしばらく壊れたガラス画面を使うのは指が危なかった。
- とりあえず翌日10時にAppleストアで予約が取れなければ修理屋に頼むことに決めた
(翌日)予約状況の確認
- 8時頃見てみたら、本日受付が可能な店舗がちらほら!
- ただしもたもたしてるとすぐになくなってしまう。
- これは10時まで待つ必要ないな!と思い、こまめに予約状況を更新
- 行きやすい店舗で当日予約枠が表示されたので、予約ゲット(確認画面もなくすぐに取れ、メールが届いた)
予約時間に店舗へ(ビックロ新宿東口店)
- 15分程度待ったが、受付をしてくれた。
- 予約が無い当日受付もいたが、やはりかなり待つ模様
- 諸々説明を受け、指定の時間に取りに来るよう言われる。約1時間後だった。
- バッテリー交換もしてくれるとの事なので、お願いした(サポートに入っているので無償)
指定の時間に受け取りへ
- 30分くらい待ったが、無事画面が綺麗になったiPhoneを取り戻した!
感想
最近はバッテリー交換のために予約がいっぱいとのことだが、 中1日で完全に復活してくれて大変助かりました。
近くに対応してくれる店舗があって良かったが、 そうでない人は配送になると1週間くらいは手元にない状況になるのだろうか。。
あと予約が基本的にネット経由なので、普段ネットを使わない人だと手間取りそう。 サポートアプリもあるけど、もし本体が壊れてたら見えないし、 パソコンが無い人もいるだろうから。。 AppleIDも普段使わないから忘れがちだし。
連休が少し潰れましたが、無事に修理してくれたので良かったです。 端末サポートは大事。
久しぶりにRailsのbundle installではまった件
windows10のホスト上にvagrantでCentOSを載せている環境。
bundle install
を実行したら以下のエラー。
$ bundle install --path=vendor/bundle ~略~ Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /vagrant/privatepj/rails42/vendor/bundle/ruby/2.3.0/gems/nokogiri-1.8.2/ext/nokogiri /root/.rbenv/versions/2.3.4/bin/ruby -r ./siteconf20180608-12060-kysmi7.rb extconf.rb checking if the C compiler accepts ... *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options. Provided configuration options: --with-opt-dir --without-opt-dir --with-opt-include --without-opt-include=${opt-dir}/include --with-opt-lib --without-opt-lib=${opt-dir}/lib --with-make-prog --without-make-prog --srcdir=. --curdir --ruby=/root/.rbenv/versions/2.3.4/bin/$(RUBY_BASE_NAME) --help --clean /root/.rbenv/versions/2.3.4/lib/ruby/2.3.0/mkmf.rb:456:in `try_do': The compiler failed to generate an executable file. (RuntimeError) You have to install development tools first. from /root/.rbenv/versions/2.3.4/lib/ruby/2.3.0/mkmf.rb:571:in `block in try_compile' from /root/.rbenv/versions/2.3.4/lib/ruby/2.3.0/mkmf.rb:522:in `with_werror' from /root/.rbenv/versions/2.3.4/lib/ruby/2.3.0/mkmf.rb:571:in `try_compile' from extconf.rb:138:in `nokogiri_try_compile' from extconf.rb:162:in `block in add_cflags' from /root/.rbenv/versions/2.3.4/lib/ruby/2.3.0/mkmf.rb:629:in `with_cflags' from extconf.rb:161:in `add_cflags' from extconf.rb:410:in `<main>' To see why this extension failed to compile, please check the mkmf.log which can be found here: /vagrant/privatepj/rails42/vendor/bundle/ruby/2.3.0/extensions/x86_64-linux/2.3.0-static/nokogiri-1.8.2/mkmf.log Text file busy @ unlink_internal - ./siteconf20180608-12060-kysmi7.rb Gem files will remain installed in /vagrant/privatepj/rails42/vendor/bundle/ruby/2.3.0/gems/nokogiri-1.8.2 for inspection. Results logged to /vagrant/privatepj/rails42/vendor/bundle/ruby/2.3.0/extensions/x86_64-linux/2.3.0-static/nokogiri-1.8.2/gem_make.out An error occurred while installing nokogiri (1.8.2), and Bundler cannot continue. Make sure that `gem install nokogiri -v '1.8.2'` succeeds before bundling.
原因はプロジェクトの作成先ディレクトリが、vagrantでWindowsと共有しているフォルダだったための模様。 確かにエラーログを見るとビルドそのものに失敗している感じだった。
こういうエラー、久しぶりだ。Rails学習の初期にWindows上に環境作ろうとしてよくはまってた。 最近は仮想環境かMac上にしか作ってなかったから油断した。
Railsのrequestで取得できる情報の具体例をまとめた
Railsのリクエスト情報取得のメソッドがたくさんあってよく分からなかったのでまとめました。
例
request.path # /book/231/reg/top ----- request.fullpath # /book/231/reg/top?uid=d4f4efb0-a2dc&type=2 request.original_fullpath # /book/231/reg/top?uid=d4f4efb0-a2dc&type=2 ----- request.url # https://test.jp/book/231/reg/top?uid=d4f4efb0-a2dc&type=2 => protocolがhttpまたはhttpsの2択 request.original_url # https://test.jp/book/231/reg/top?uid=d4f4efb0-a2dc&type=2 => base_urlを使っており、その中でschemaメソッド内を使ってget_headerメソッドでプロトコルを取得しているので、http/https以外でもOK。
解説
fullpathとoriginal_fullpathの違い
original_fullpathは環境変数"ORIGINAL_FULLPATH"に値があれば優先して使用する。 なければfullpathを使うので、ほぼ同じ意味のようです。
def original_fullpath @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath) end
urlとoriginal_urlの違い
urlの場合はプロトコルがhttpかhttpsの2択、original_urlはhttp(s)以外も扱える。
# actionpack/lib/action_dispatch/http/url.rb def url protocol + host_with_port + fullpath end def protocol @protocol ||= ssl? ? "https://" : "http://" end
# actionpack/lib/action_dispatch/http/request.rb def original_url base_url + original_fullpath end # lib/rack/request.rb def base_url url = "#{scheme}://#{host}" url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme] url end # lib/rack/request.rb def scheme if get_header(HTTPS) == 'on' 'https' elsif get_header(HTTP_X_FORWARDED_SSL) == 'on' 'https' elsif get_header(HTTP_X_FORWARDED_SCHEME) get_header(HTTP_X_FORWARDED_SCHEME) elsif get_header(HTTP_X_FORWARDED_PROTO) get_header(HTTP_X_FORWARDED_PROTO).split(',')[0] else get_header(RACK_URL_SCHEME) end end
参考 https://easyramble.com/rails-request-url-original_url-differences.html
webpack-dev-serverが起動できず Unexpected token: name
webpackでローカルサーバを起動するためにwebpack-dev-serverを起動しようとしたら
Unexpected token: name (urlParts)
というエラーが出た。
日本語の解決記事が見つからなかったので残す。ちなみに原因はバージョン差異ということ以外は不明。。
環境
Mac OS X 10.11.6 npm 5.4.2 node v8.7.0 (nodebrewで管理)
経緯
# package.json { "scripts": { "build": "webpack", "start": "webpack-dev-server" }, "devDependencies": { "webpack": "^3.8.0" } } # webpack.config.js var webpack = require('webpack'); module.exports = { entry: './src/main.js', output: { path: `${__dirname}/build`, filename: 'bundle.js' }, devServer: { contentBase: 'build', port: 8081 }, devtool: 'source-map', plugins: [ // JSファイルのminifyを実行する new webpack.optimize.UglifyJsPlugin({ // minify時でもソースマップを利用する sourceMap: true }) ] };
上記のpackage.jsonでサーバを起動したら、下記エラーが発生。
$ npm run start > @ start /Users/hogehoge/study/webpack/myproject > webpack-dev-server (...省略...) ERROR in bundle.js from UglifyJs Unexpected token: name (urlParts) [(webpack)-dev-server/client?http:/localhost:8081:24,0][bundle.js:3694,4] webpack: Failed to compile.
ググった結果、webpack-dev-serverのバージョンが新しいため?との情報 を得たので、 バージョンを落として再度インストールしてみた。 https://github.com/webpack/webpack-dev-server/issues/1101
$ npm uninstall webpack-dev-server $ npm install -D webpack-dev-server@2.7.1
再度npm run start
を実行すると、無事にサーバが起動した。
なおバージョンはどこまであげられるのか試したところ、2.8.0では同様のエラーとなってしまったため、 2.7系の最新である2.7.1までという結果になった。