Symfoware

Symfowareについての考察blog

elixirからepgsqlを使用して、PostgreSQLに接続する

elixirからPostgreSQLに接続しています。
OSはFreeBSD 10.1
PostgreSQL 9.4.4
elixir 1.0.5

こんな環境です。


postgrex



最初、postgrexというライブラリを使おうとおもたのですが、
https://github.com/ericmj/postgrex

どうしてもエラーが消せず、諦めました。


17:38:12.237 [error] GenServer #PID<0.61.0> terminating
Last message: {:tcp, #Port<0.2294>,...
State: %{backend_key: nil, bootstrap: false, extensions: [{Postgrex.Extensions.Binary, nil}, H...
** (exit) an exception was raised:
    ** (ErlangError) erlang error: {:noproc, {GenServer, :call, ...
        (elixir) lib/gen_server.ex:356: GenServer.call/3
        lib/postgrex/protocol.ex:34: Postgrex.Protocol.bootstrap/1
        lib/postgrex/connection.ex:417: Postgrex.Connection.new_data/2
        lib/postgrex/connection.ex:292: Postgrex.Connection.handle_info/2
        (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
        (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
        (stdlib) proc_lib.erl:239: :proc_lib.init_p_do_apply/3







epgsql



https://github.com/epgsql/epgsql

Erlang用のドライバーですが、elixirからも使用できます。
FreeBSDだと、パッケージが用意されていました。


# pkg search epgsql
erlang-epgsql-3.1.0_2




pkg installでインストールしています。


# pkg install erlang-epgsql







サンプル



サンプルはこんな感じになりました。

接続パラメーターを文字列ではなく、String.to_char_listで、
変換してから指定するのがポイントです。


  1. # データベース接続開始
  2. {:ok, pid} = :epgsqla.start_link()
  3. IO.inspect :epgsqla.connect(pid,
  4.     String.to_char_list("localhost"),
  5.     String.to_char_list("pgadmin"),
  6.     String.to_char_list("P@ssw0rd"),
  7.     [{:database, String.to_char_list("sample")}]
  8. )
  9. # 削除クエリー
  10. {:ok, _} = :epgsql.squery(pid, "delete from test")
  11. # 登録クエリー
  12. {:ok, _} = :epgsql.squery(pid, "insert into test(id, name) values (1, 'test')")
  13. {:ok, _} = :epgsql.squery(pid, "insert into test(id, name) values (2, '日本語テスト')")
  14. # 検索して結果を表示
  15. {:ok, field_info, rows} = :epgsql.squery(pid, "select id,name from test")
  16. Enum.each(rows, fn(row) ->
  17.     {id, name} = row
  18.     IO.puts "id:#{id}, name:#{name}"
  19. end)
  20. # 接続を閉じる
  21. :epgsql.close(pid)




実行結果


# elixir sample.exs
sample.exs:20: warning: variable field_info is unused
#Reference<0.0.1.76>
id:1, name:test
id:2, name:日本語テスト


テーマ:プログラミング - ジャンル:コンピュータ

  1. 2015/08/02(日) 18:33:47|
  2. Erlang
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

elixir + cowboyでwebアプリ その6 画像のアップロードとpostの最大値変更

elixir + cowboyで、バイナリファイルをアップロードする機能を
実装してみます。


サンプル



mix new imageとして、新しくアプリケーションを作成しました。
ソースはこんな感じです。

・mix.exs


  1. defmodule Image.Mixfile do
  2. use Mix.Project
  3. def project do
  4.     [app: :image,
  5.      version: "0.0.1",
  6.      elixir: "~> 1.0",
  7.      build_embedded: Mix.env == :prod,
  8.      start_permanent: Mix.env == :prod,
  9.      deps: deps]
  10. end
  11. def application do
  12.     [
  13.         mod: {Image, []},
  14.         applications: [:logger, :cowboy]
  15.     ]
  16. end
  17. defp deps do
  18.     []
  19. end
  20. end




・lib/image.ex


  1. defmodule Image do
  2. def start(_type, _args) do
  3.     dispatch = :cowboy_router.compile([
  4.      { :_, [
  5.          {"/upload", Image.Handler, []},
  6.      ]}
  7.     ])
  8.     
  9.     {:ok, _} = :cowboy.start_http(:http, 100, [{:port, 8080}], [{:env, [{:dispatch, dispatch}]}])
  10.     ImageServer.Supervisor.start_link
  11. end
  12. end





・lib/imageServer.ex


  1. defmodule ImageServer.Supervisor do
  2. use Supervisor
  3. def start_link() do
  4.     {:ok, sup} = Supervisor.start_link(__MODULE__, :ok)
  5. end
  6. def init(:ok) do
  7.     {:ok, {{:one_for_one, 10, 10}, []}}
  8. end
  9. end





・lib/imageHandler.ex


  1. defmodule Image.Handler do
  2. def init(_type, req, []) do
  3.     {:ok, req, :no_state}
  4. end
  5. def handle(request, state) do
  6.     
  7.     # requestからPOSTデータを取得
  8.     {:ok, image, _} = :cowboy_req.body(request)
  9.     {:ok, file} = File.open("image.jpg", [:write])
  10.     IO.binwrite(file, image)
  11.     File.close(file)
  12.     
  13.     { :ok, reply } = :cowboy_req.reply(
  14.      200,
  15.      [ {"content-type", "text/html"} ],
  16.      "ok\n",
  17.      request
  18.     )
  19.     {:ok, reply, state}
  20. end
  21. def terminate(reason, request, state) do
  22.     :ok
  23. end
  24. end





テスト用のプログラムはPythonで作成しました。

・sample.py


  1. # -*- coding:utf-8 -*-
  2. import urllib
  3. import urllib2
  4. import json
  5. def main():
  6.     url = 'http://192.168.1.102:8080/upload'
  7.     with open('cc2.jpg', 'rb') as f:
  8.         postval = f.read()
  9.     
  10.     request = urllib2.Request(url, data=postval)
  11.     response = urllib2.urlopen(request)
  12.     ret = response.read()
  13.     
  14.     print ret
  15. if __name__ == '__main__':
  16.     main()





サーバーを起動。


# iex -S mix




Pythonのスクリプトを実行すると、画像がサーバー側に保存されるはずです。
意外とお手軽でした。


$ python sample.py
ok







8MBを超えるファイル



容量の大きいファイルをアップロードしようとすると、こんなエラーになります。


13:27:00.418 [error] Process #PID<0.231.0> raised an exception
** (ErlangError) erlang error: [reason: {:badmatch, ...
    (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4





調べてみると、POSTの上限は8MBの模様。
こちらを参考に、上限を変更します。
http://ninenines.eu/docs/en/cowboy/HEAD/guide/req_body/

・lib/imageHandler.ex


  1. defmodule Image.Handler do
  2. def init(_type, req, []) do
  3.     {:ok, req, :no_state}
  4. end
  5. def handle(request, state) do
  6.     
  7.     # requestからPOSTデータを取得
  8.     # lengthオプションで、受け取るバイト数の上限を指定
  9.     {:ok, image, _} = :cowboy_req.body(request, [{:length, 20000000}] )
  10.     {:ok, file} = File.open("image.jpg", [:write])
  11.     IO.binwrite(file, image)
  12.     File.close(file)
  13.     
  14.     { :ok, reply } = :cowboy_req.reply(
  15.      200,
  16.      [ {"content-type", "text/html"} ],
  17.      "ok\n",
  18.      request
  19.     )
  20.     {:ok, reply, state}
  21. end
  22. def terminate(reason, request, state) do
  23.     :ok
  24. end
  25. end




これで、大きな容量のファイルもアップロードできるようになりました。






リクエストヘッダー



画像のファイル名をリクエストヘッダーに含めてみます。
こちらを参考にしました。
http://ninenines.eu/docs/en/cowboy/HEAD/manual/cowboy_req/


・lib/imageHandler.ex


  1. defmodule Image.Handler do
  2. def init(_type, req, []) do
  3.     {:ok, req, :no_state}
  4. end
  5. def handle(request, state) do
  6.     
  7.     # ヘッダーからファイル名を取得
  8.     {file_name, _} = :cowboy_req.header("file_name", request)
  9.     
  10.     # requestからPOSTデータを取得
  11.     # lengthオプションで、受け取るバイト数の上限を指定
  12.     {:ok, image, _} = :cowboy_req.body(request, [{:length, 20000000}] )
  13.     {:ok, file} = File.open(file_name, [:write])
  14.     IO.binwrite(file, image)
  15.     File.close(file)
  16.     
  17.     { :ok, reply } = :cowboy_req.reply(
  18.      200,
  19.      [ {"content-type", "text/html"} ],
  20.      "ok\n",
  21.      request
  22.     )
  23.     {:ok, reply, state}
  24. end
  25. def terminate(reason, request, state) do
  26.     :ok
  27. end
  28. end





送信側のサンプル

・sample.py


  1. # -*- coding:utf-8 -*-
  2. import urllib
  3. import urllib2
  4. import json
  5. def main():
  6.     url = 'http://192.168.1.102:8080/upload'
  7.     with open('cc2.jpg', 'rb') as f:
  8.         postval = f.read()
  9.     
  10.     request = urllib2.Request(url, data=postval)
  11.     # ヘッダーにファイル名を付与
  12.     request.add_header('file_name', 'cc2.jpg')
  13.     
  14.     response = urllib2.urlopen(request)
  15.     ret = response.read()
  16.     
  17.     print ret
  18. if __name__ == '__main__':
  19.     main()




これで、ちゃんとヘッダーに仕込んだファイル名で保存してくれました。
最終的なサンプルをこちらに置いておきます。

elixir + cowboy 画像のアップロードとpostの最大値変更

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2015/08/02(日) 17:32:38|
  2. Erlang
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

elixir + cowboyでwebアプリ その5 デプロイ

elixir + cowboyでapiサーバーを作ってみました。
elixir + cowboyでwebアプリ その4 住所検索APIサンプル

これ、実運用するときどうするんだろう。
まさかiex -S mixって起動するわけじゃ・・・

ということで、デプロイの方法について調べてみます。


exrm



デプロイはこちらを参考にしました。
Application Deployment

Elixir Release Manager (exrm)を使えとのこと。
https://github.com/bitwalker/exrm


FreeBSD 10.1には、pkgが用意されているようです。


# pkg search exrm
elixir-exrm-0.18.5



これをインストールして使用することにしました。


# pkg install elixir-exrm
Updating FreeBSD repository catalogue...
FreeBSD repository is up-to-date.
All repositories are up-to-date.
The following 11 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
    elixir-exrm: 0.18.5
    relx: 3.4.0
    rebar3: 20150630_1
    erlang-ssl_verify_hostname: 1.0.5
    erlang-rebar3_hex: 0.1.0
    erlang-providers: 1.4.1
    erlang-getopt: 0.8.2
    erlang-erlware_commons: 0.15.0
    erlang-bbmustache: 1.0.3
    elixir-conform: 0.15.0
    erlang-neotoma: 1.7.2_1




結構、色んな物に依存してますね。

インストールが終わったら、「mix help」を実行してみます。
「mix release」が追加されているはずです。


# mix help
mix                 # Run the default task (current: mix run)
mix app.start         # Start all registered apps
mix archive         # List all archives
...
mix new             # Create a new Elixir project
mix release         # Build a release for the current mix application.
mix release.clean     # Clean up any release-related files.
mix release.plugins # View information about active release plugins









リリースビルド



ビルドすると、必要なライブラリが一つのフォルダにまとめられ、
そのライブラリを使用して起動するようになります。

そのため、mix.exsのapplicationsに依存しているライブラリを列挙しておきます。

・mix.exs


  1. defmodule Cowjson.Mixfile do
  2. use Mix.Project
  3. def project do
  4.     [app: :cowjson,
  5.      version: "0.0.1",
  6.      elixir: "~> 1.0",
  7.      build_embedded: Mix.env == :prod,
  8.      start_permanent: Mix.env == :prod,
  9.      deps: deps]
  10. end
  11. def application do
  12.     [
  13.         mod: {Cowjson, []},
  14.         applications: [:logger, :cowboy, :mariaex, :exjsx]
  15.     ]
  16. end
  17. defp deps do
  18.     []
  19. end
  20. end




mix releaseを実行。


# mix release
==> Building release with MIX_ENV=dev.
==> Generating relx configuration...
==> Generating sys.config...
==> Generating boot script...
==> Generating release...
==> Generating nodetool...
==> Packaging release...
==> The release for cowjson-0.0.1 is ready!
==> You can boot a console running your release with `$ rel/cowjson/bin/cowjson console`





relディレクトリが作成され、必要なライブラリがまとめられました。

631_01.png


コンソールモードで起動してみます。

rel/[アプリケーション名]/bin/[アプリケーション名]
例えば「rel/my_app/bin/my_app」に実行可能なバイナリが配置されています。

consoleオプション付きで起動してみます。


# rel/cowjson/bin/cowjson console
/var/dev/elixir/cowjson/rel/cowjson/running-config
Exec: /var/dev/elixir/cowjson/rel/cowjson/erts-7.0.2/bin/erlexec -boot /var/dev/elixir/cowjson/rel/cowjson/releases/0.0.1/cowjson -boot_var ERTS_LIB_DIR /var/dev/elixir/cowjson/rel/cowjson/erts-7.0.2/../lib -env ERL_LIBS /var/dev/elixir/cowjson/rel/cowjson/lib -config /var/dev/elixir/cowjson/rel/cowjson/running-config/sys.config -pa /var/dev/elixir/cowjson/rel/cowjson/lib/consolidated -args_file /var/dev/elixir/cowjson/rel/cowjson/running-config/vm.args -user Elixir.IEx.CLI -extra --no-halt +iex -- console
Root: /var/dev/elixir/cowjson/rel/cowjson
/var/dev/elixir/cowjson/rel/cowjson
Erlang/OTP 18 [erts-7.0.2] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(cowjson@127.0.0.1)1>




起動してくれました。
通常のiexと同様、Ctrl + cからabortでシュル用できます。


コンソール無しで起動するにはstartオプションを指定。


# rel/cowjson/bin/cowjson start



これでバックグラウンドで実行してくれます。


停止は、stopオプションを付けて実行。


# rel/cowjson/bin/cowjson stop






適用



実際にサーバーへ適用するときは、releasesの中に
全資産のアーカイブが作成されています。

rel/my_app/releases/0.0.1/my_app-0.0.1.tar.gz

631_02.png


これをサーバーにコピーして解凍することで、
適用を行うことになるようです。


起動スクリプトやnginxとの連携はまた後日。

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2015/07/28(火) 23:49:09|
  2. Erlang
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

elixir + cowboyでwebアプリ その4 住所検索APIサンプル

色々材料が揃ってきました。

elixir + cowboyでwebアプリ その3 json API
elixir CSVファイルを1行づつ読み込み、内容をMariaDBに登録する

多少実用的なサンプルとして、郵便番号を受け取るとデータベースを検索し、
結果をjsonで返却するサンプルを書いてみます。


ソースコード



プログラムは、ここで作ったものを修正します。

elixir + cowboyでwebアプリ その3 json API


修正対象は、lib/cowjsonHandler.exです。

・cowjsonHandler.ex


  1. defmodule Cowjson.Handler do
  2. def init(_type, req, []) do
  3.     {:ok, req, :no_state}
  4. end
  5. def handle(request, state) do
  6.     
  7.     # requestからPOSTデータを取得
  8.     {:ok, postvals, req2} = :cowboy_req.body_qs(request)
  9.     
  10.     # 値を取得
  11.     zip = :proplists.get_value("zip", postvals)
  12.     {:ok, address} = get_address(zip)
  13.     
  14.     
  15.     # クライアントに返す値
  16.     api_result = %{"zip" => zip, "address" => address}
  17.     
  18.     # JSXでデコード
  19.     {:ok, api_result_json} = JSX.encode(api_result)
  20.     
  21.     # 内容を返却
  22.     { :ok, reply } = :cowboy_req.reply(
  23.      200,
  24.      [ {"content-type", "application/json"} ],
  25.      api_result_json,
  26.      request
  27.     )
  28.     {:ok, reply, state}
  29. end
  30. defp get_address(zip) do
  31.     # データベースに接続
  32.     {:ok, p} = Mariaex.Connection.start_link(username: "admin", database: "sample")
  33.     # データの検索
  34.     {:ok, result} = Mariaex.Connection.query(p, "SELECT address FROM post where zip_code = ?", [zip])
  35.     
  36.     address = ""
  37.     
  38.     row = List.first(result.rows)
  39.     if row != nil do
  40.         [address] = row    
  41.     end
  42.     
  43.     # データベース切断
  44.     Mariaex.Connection.stop(p)
  45.     
  46.     {:ok, address}
  47.     
  48.     
  49. end
  50. def terminate(reason, request, state) do
  51.     :ok
  52. end
  53. end





テスト用のプログラムはPythonで作成しました。

・sample.py


  1. # -*- coding:utf-8 -*-
  2. import urllib
  3. import urllib2
  4. import json
  5. def main():
  6.     url = 'http://192.168.1.102:8080/api'
  7.     data = {
  8.         u'zip' : u'9010154',
  9.     }
  10.     postval = urllib.urlencode(data)
  11.     
  12.     request = urllib2.Request(url, data=postval)
  13.     response = urllib2.urlopen(request)
  14.     ret = response.read()
  15.     
  16.     retjson = json.loads(ret)
  17.     print retjson['zip']
  18.     print retjson['address']
  19. if __name__ == '__main__':
  20.     main()





実行してみると、ちゃんと住所の応答があります。


$ python sample.py
9010154
沖縄県那覇市赤嶺





課題は、毎回データベースに接続している点でしょうか。
とりあえず、動くサンプルということで。


テーマ:プログラミング - ジャンル:コンピュータ

  1. 2015/07/28(火) 22:51:59|
  2. Erlang
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

elixir + cowboyでwebアプリ その3 json API

elixir + cowboyでGETやPOSTデータを取得する方法を調べてみました。
elixir + cowboyでwebアプリ その2 GET,POSTパラメーター

json形式のデータを受け取り、応答してくれるAPIを書いてみます。


exjsx



json形式へのエンコードはexjsxを使うことにしました。
elixir + exjsxでjsonのエンコード、デコード




mix.exs



このサンプルのためにmix newしました。

# mix new cowjson


mix.exsはこんな感じです。
※環境によっては、depsにcowboyやexjsxが必要だと思います。


・mix.exs


  1. defmodule Cowjson.Mixfile do
  2. use Mix.Project
  3. def project do
  4.     [app: :cowjson,
  5.      version: "0.0.1",
  6.      elixir: "~> 1.0",
  7.      build_embedded: Mix.env == :prod,
  8.      start_permanent: Mix.env == :prod,
  9.      deps: deps]
  10. end
  11. def application do
  12.     [
  13.         mod: {Cowjson, []},
  14.         applications: [:logger, :cowboy]
  15.     ]
  16. end
  17. defp deps do
  18.     []
  19. end
  20. end





その他のソース



自動生成されたlib/cowjson.ex

・lib/cowjson.ex


  1. defmodule Cowjson do
  2. def start(_type, _args) do
  3.     dispatch = :cowboy_router.compile([
  4.      { :_, [
  5.          {"/api", Cowjson.Handler, []},
  6.      ]}
  7.     ])
  8.     
  9.     {:ok, _} = :cowboy.start_http(:http, 100, [{:port, 8080}], [{:env, [{:dispatch, dispatch}]}])
  10.     CowjsonServer.Supervisor.start_link
  11. end
  12. end





・lib/cowjsonServer.ex


  1. defmodule CowjsonServer.Supervisor do
  2. use Supervisor
  3. def start_link() do
  4.     {:ok, sup} = Supervisor.start_link(__MODULE__, :ok)
  5. end
  6. def init(:ok) do
  7.     {:ok, {{:one_for_one, 10, 10}, []}}
  8. end
  9. end






API本体です。
とりあえず、受け取ったデータをそのまま返しています。

・cowjsonHandler.ex


  1. defmodule Cowjson.Handler do
  2. def init(_type, req, []) do
  3.     {:ok, req, :no_state}
  4. end
  5. def handle(request, state) do
  6.     
  7.     # requestからPOSTデータを取得
  8.     {:ok, postvals, req2} = :cowboy_req.body_qs(request)
  9.     
  10.     # 値を取得
  11.     name = :proplists.get_value("name", postvals)
  12.     address = :proplists.get_value("address", postvals)
  13.     
  14.     # クライアントに返す値
  15.     # とりあえず、受け取った値をそのままリターン
  16.     api_result = %{"req_name" => name, "req_address" => address}
  17.     
  18.     # JSXでデコード
  19.     {:ok, api_result_json} = JSX.encode(api_result)
  20.     
  21.     # 内容を返却
  22.     { :ok, reply } = :cowboy_req.reply(
  23.      200,
  24.      [ {"content-type", "application/json"} ],
  25.      api_result_json,
  26.      request
  27.     )
  28.     {:ok, reply, state}
  29. end
  30. def terminate(reason, request, state) do
  31.     :ok
  32. end
  33. end




コンバイル&起動を行います。


# iex -S mix







クライアントサンプル



テスト用のクライアントサンプルはPythonで記載しました。

・sample.py


  1. # -*- coding:utf-8 -*-
  2. import urllib
  3. import urllib2
  4. import json
  5. def main():
  6.     url = 'http://192.168.1.102:8080/api'
  7.     data = {
  8.         u'name' : u'symfo',
  9.         u'address' : u'symfoware@example.com'
  10.     }
  11.     postval = urllib.urlencode(data)
  12.     request = urllib2.Request(url, data=postval)
  13.     response = urllib2.urlopen(request)
  14.     ret = response.read()
  15.     
  16.     print ret
  17.     
  18.     retjson = json.loads(ret)
  19.     print retjson['req_name']
  20.     print retjson['req_address']
  21. if __name__ == '__main__':
  22.     main()





実行してみると、狙い通りの応答が得られています。


$ python sample.py
{"req_address":"symfoware@example.com","req_name":"symfo"}
symfo
symfoware@example.com






ソースはこちらに。
elixir + cowboy + exjsxを使用したjson APIのサンプル

※Bitbucket、いつの間にかスニペットが使えるようになってました。

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2015/07/28(火) 21:58:36|
  2. Erlang
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
次のページ