この記事は、「Elixir Advent Calendar 2019」21日目の記事です。
昨日は @enerickさんの「params 入門」でした。
はじめに
折角Elixirを触り始めたので、PhoenixとLiveviewも試してみました。
思ったより風呂敷が広がり過ぎてしまったので分けます。
当初、某合宿イベントのWebサイトと運営全般を一括管理できるCMS的なものを作ろうと思っていたのですが、LiveViewを触り始めた頃に、試しにTextareaにElixirを食わして、eval_stringした結果を反映、みたいなことをやってみると思った以上にサクサク動いて惚れてしまいました。
LiveViewでのコード評価
まずプロジェクトを作ります。
mix phx.new liveeval --no-ecto
> Fetch and install dependencies? [Yn] Y
Liveview環境の下準備
次にLiveviewの設定をしていきます。
とりあえずhttps://github.com/phoenixframework/phoenix_live_view/blob/master/guides/introduction/installation.md の手順通りで。
英語が苦手でもElixirのトコは読めますよね。私も英語はさっぱりですが、コードを拾い読むだけで割となんとかなります。日本語の情報は古くなっているものもあるので、困ったらまず原典です。
まず、mix.exsにLiveviewを入れます。githubのURLを指定する方法もありますが、結構ライブラリが更新されるようなので、素直にバージョン指定した方が無難です。
defp deps do
[
... # 前半省略
{:phoenix_live_view, "~> 0.4.1"},
{:floki, ">= 0.0.0", only: :test}
]
end
saltの生成
mix phx.gen.secret 32
> AaLfpv964DzcET6IFfkM/vLXcrmKaH6D # <これをコピーしておく
生成された塩をconfig/config.exに設定とともに追加します。
# Configures the endpoint
config :liveeval, LiveevalWeb.Endpoint,
url: [host: "localhost"],
live_view: [signing_salt: "AaLfpv964DzcET6IFfkM/vLXcrmKaH6D"] # <この行を追加し、後半の文字列を先ほどの文字列で置き換える
secret_key_base: "***ここはデフォルトのまま***",
render_errors: [view: LiveevalWeb.ErrorView, accepts: ~w(html json)],
pubsub: [name: Liveeval.PubSub, adapter: Phoenix.PubSub.PG2],
次、/lib/liveeval/routes.ex。一行追加します。
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug Phoenix.LiveView.Flash #<ここに追加
plug :protect_from_forgery
plug :put_secure_browser_headers
end
ここはパイプライン的に評価されるようなので、順番大事です。(一度ハマりました)
lib/liveeval_web.ex、controller, view, routerにそれぞれ設定を加えます。
def controller do
quote do
...
import Phoenix.LiveView.Controller
end
end
def view do
quote do
...
import Phoenix.LiveView,
only: [live_render: 2, live_render: 3, live_link: 1, live_link: 2,
live_component: 2, live_component: 3, live_component: 4]
end
end
def router do
quote do
...
import Phoenix.LiveView.Router
end
end
/lib/liveeval_web/Endpoint.exの最初の方に追加し、最後のPlug.Sessionのあたりを修正します。
defmodule Liveeval.Endpoint do
use Phoenix.Endpoint
@session_options [
store: :cookie,
key: "_liveeval_key",
signing_salt: "Bqw6yIXz"
]
socket "/live", Phoenix.LiveView.Socket,
websocket: [connect_info: [session: @session_options]]
...
plug Plug.Session, @session_options # 変更
end
LiveView_pageの追加
defmodule LiveevalWeb.LivePage do
use Phoenix.HTML
use Phoenix.LiveView
def render(assigns) do
~L"""
<form phx-change="change" >
<input type="text" name="text" value=<%= @text %> ></input>
</form>
<%= @text %>
"""
end
def mount(_session, socket) do
{:ok, assign(socket, text: "")}
end
def handle_event("change", %{"text" => text}, socket) do
{:noreply, assign(socket, text: text)}
end
end
js周り
"dependencies": {
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view" <この行を追加
},
package.jsonを書き換えたので更新しておきましょう。
$ npm install --prefix assets
import { Socket } from 'phoenix';
import LiveSocket from 'phoenix_live_view';
import css from "../css/app.css"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken }});
liveSocket.connect()
@import "../../deps/phoenix_live_view/assets/css/live_view.css";
多分こんな感じでLiveViewが動く状態になってるはずです。一度実行してみましょう。
$ mix phx.server
テキストボックスが表示され、入力したテキストがすぐ下に反映されれば成功です。
LiveViewでCode.string_evalするコード
変更ファイル一覧
- assets/
- css/app.css
- css/phoenix.css
- js/app.js
- lib/liveeval_web/
- controllers/page_controller.ex
- live/live_page.ex
- router.ex
詳細は、このコミットを参照ください。
少しややこしいlive_page.exとapp.jsだけ掲載します。
import { Socket } from 'phoenix';
import LiveSocket from 'phoenix_live_view';
import css from "../css/app.css"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let Hooks = {}
Hooks.MyHook = {
mounted() {
this.el.addEventListener("input", e => {
this.pushEvent("change", { "code": this.el.value })
})
}
}
let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken }, hooks: Hooks });
liveSocket.connect()
"phx-change"では<form>
タグにしか付与できず、テキストエリアのイベントを拾えません。<form>
のままでもいいのですが、Enter入力時にページが遷移してしまい、内容が消えてしまいます。
"phx-hook"を利用することで、任意のjavascriptと関連付けられ、mountedでロードされたタイミングをjs側でhookできます。またliveviewにイベントを投げることもできます。
ここではtextareaのinputイベントを拾い、change
というイベント名で、入力内容をliveviewに送信しています。
defmodule LiveevalWeb.LivePage do
use Phoenix.HTML
use Phoenix.LiveView
def render(assigns) do
~L"""
<textarea phx-hook="MyHook" class="elixir-codeblock" name="code" value="<%= @code %>" placeholder="input elixir code."> </textarea>
<div class="result-block <%= @status %>"><%= @result %></div>
"""
end
def handle_params(%{"id" => id}, _url, socket) do
socket = assign(socket, id: id)
{:noreply, socket}
end
def mount(_session, socket) do
{:ok, assign(socket, code: "", result: "", status: "empty")}
end
def handle_event("change", %{"code" => code}, socket) do
send(self(), {:submit, code})
{:noreply, assign(socket, code: code)}
end
def handle_info({:submit, code}, socket) do
cleansing = String.replace(code, "System", "")
[status, result] =
try do
{term, _} = Code.eval_string(cleansing)
case term do
nil -> ["empty", term]
_ -> ["ok", Kernel.inspect(term)]
end
rescue
e in _ ->
["error", Kernel.inspect(e)]
end
{:noreply, assign(socket, result: result, status: status)}
end
end
liveview側ではhandle_eventで受け取り、評価結果をstatus, resultとしてhtmlに反映しています。
これで、ブラウザ上で入力したElixirコードが即時評価され、結果が表示されます。
でもeval_stringって・・
Code.eval_stringの説明に以下のような警告があります。
Warning: string can be any Elixir code and will be executed with the same privileges as the Erlang VM: this means that such code could compromise the machine (for example by executing system commands). Don't use eval_string/3 with untrusted input (such as strings coming from the network).
(【適当訳】けいこく:突っ込んだコードはVMと同権限で動きます。例えばSystem commandとかも実行できちゃいます。信用できるコード以外は突っ込んじゃだめです。ネットワーク越しに食わせるとかすんなよ!)
とのことです。危ないです。
でも、リアルタイムにElixirが評価されるのめっちゃ楽しい、この楽しさを共有したい・・。
とりあえず、評価前に気休め程度にSystemだけでも排除しときましょう。
sanitized = String.replace(code,"System","")
とりあえず素直にはSystem.haltとかやっても落ちなくなりました。アレコレやれば抜けれそうですが、そこまでしてシステムを落としたい人はElixir好きの人たちの中には居ないと信じてます。
とはいえ、このまま公開するのも怖いです。もう少し悪いことがしにくい環境を作りましょう。っと、この先も結構長くなってしまったので別記事に分けます。(すみません、完成まではたどり着けませんでした。)
- 次回 → LiveViewでElixirを評価する(2)
- github → https://github.com/s-hosoai/liveeval
おわりに
ノリで始めたら結構大きくなってしまい記事化するのが大変でしたが、Dockerとの連携など個人的にいろいろと勉強になりました。
Elixirは完全に理解したんですが、フロントエンドは何が分からないのか分からない状態で混乱します。
類似サービスは多くあるものの、やっぱりLiveviewのリアルタイム評価は面白いですね。
Elixirの教育やオンラインでのやり取りに結構いいプラットフォームになるんじゃないかと考えています。実運用するとなるとそれなりにマシンスペックが要りそうなので、どこかにホスティングして貰えませんかね。
今後、以下のような拡張を妄想してます。
- 入力コードの永続化
- 複数人が同じコードをリアルタイム共有
- VM間(別コード間)の通信
- 外部ライブラリの読み込み
- git/gist連携
明日は@piacerexさんです。