スポンサーリンク

2016年2月15日

ElixirからSSLモジュール(Erlang)を使い、smtp.gmail.comに接続しメールを送る

Goal

  • ElixirからSSLモジュール(Erlang)を使い、smtp.gmail.com:465へ接続する
  • ElixirからSSLモジュール(Erlang)を使い、Gmailのアカウントへメールを送信する

Dev-Environment

OS: Windows8.1
Erlang: Eshell V7.2.1, OTP-Version 18.1
Elixir: v1.2.0

Text

前回は、ErlangのSSLモジュールを使ってsmtp.gmail.com:465に接続し、
自分のGmailアカウントへメールを送信することをやりました。
今回は、ElixirからSSLモジュール(Erlang)を使って同じことをやります。
前回、ErlangでSSLモジュールを使うために、
幾つかApplication.start/1をしましたね。
今回は、mixファイルのapplication/0に記述して自動で起動させます。
“:asn1”、”:crypto”、”:public_key”、”:ssl”を追加してください。

File: mix.exs

defmodule SendGmail.Mixfile do
  use Mix.Project

  def project do
    [app: :send_gmail,
     version: "0.0.1",
     elixir: "~> 1.2",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end

  def application do
    [applications: [:logger, :asn1, :crypto, :public_key, :ssl]]
  end

  defp deps do
    []
  end
end
実際の変換したコードを見る前に前提知識となる部分を記載します。
「知ってるよ!」って人は読み飛ばしてください。
大体がErlang側の文法(?)についてになりますので…
  • Erlangでのダブルクォーテーションの扱い
Erlangでダブルクォーテーションで囲むと文字リストとして扱われます。

Example:

"abc". => [97, 98, 99]

> [97, 98, 99] = "abc".
"abc"
> [97, 98, 99] == "abc".
true
  • 文字リストを++した場合
文字リストは整数のリストになるため、
リストに要素を追加するときに使う++でも追加できる。

Example:

iex> 'aaa' ++ '\r\n'
'aaa\r\n'
iex> "aaa" ++ "\r\n"
** (ArgumentError) argument error
    :erlang.++("aaa", "\r\n")
iex> "aaa" <> "\r\n"
"aaa\r\n"
  • Elixirで標準に用意されている関数名とコンフリクトした場合の回避方法

Example:

Kernelのsend/2と関数名が同じなため衝突した。
対処法としては関数名を別にするか、importで除外してやればいい。
== Compilation error on file lib/send_gmail.ex ==
** (CompileError) lib/send_gmail.ex:1: imported Kernel.send/2 conflicts with local function
    (elixir) src/elixir_locals.erl:140: :elixir_locals."-ensure_no_import_conflict/4-lc$^0/1-0-"/3
    (elixir) src/elixir_locals.erl:139: anonymous fn/4 in :elixir_locals.ensure_no_import_conflict/4
    (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6

defmodule Example do
  import Kernel, except: [send: 2]

  ...
end
他にも__MODULE__を使った方法があった気がする…詳しく調べてないので知らん
では、本題に入りましょう。
まず、Elixir側に変換したソースコードはこんな感じです。

File: send_gmail.ex

defmodule SendGmail do
  import Kernel, except: [send: 2]

  def connect do
    case :ssl.connect('smtp.gmail.com', 465, [{:active, false}], 1000) do
      {:ok, socket} ->
        recieve(socket)
        send(socket, 'HELO localhost')
        send(socket, 'AUTH LOGIN')
        send(socket, :erlang.binary_to_list(:base64.encode('your_gmail_account@gmail.com')))
        send(socket, :erlang.binary_to_list(:base64.encode('application_specific_password')))
        send(socket, 'MAIL FROM: <from@gmail.com>')
        send(socket, 'RCPT TO:<to@gmail.com>')
        send(socket, 'DATA')
        send_no_receive(socket, 'From: <from@gmail.com>')
        send_no_receive(socket, 'To: <to@gmail.com>')
        send_no_receive(socket, 'Date: Mon, 15 Feb 2016 21:31:00 +0900')
        send_no_receive(socket, 'Subject: Noreply')
        send_no_receive(socket, '')
        send_no_receive(socket, 'This is a test mail message')
        send_no_receive(socket, '')
        send(socket, '.')
        send(socket, 'QUIT')
        :ssl.close(socket)
      {:error, reason} -> {:error, reason}
      _ -> "connection falid."
    end
  end

  defp send_no_receive(socket, data) do
    :ssl.send(socket, data ++ '\r\n')
  end

  defp send(socket, data) do
    :ssl.send(socket, data ++ '\r\n')
    recieve(socket)
  end

  defp recieve(socket) do
    case :ssl.recv(socket, 0, 1000) do
      {:ok, result} -> IO.inspect(result)
      {:error, reason} -> IO.inspect(reason)
    end
  end
end
すごく…Erlangばかり…
前回のコードと変わらん(笑)
実行結果はこんな感じです。

Example:

> iex -S mix

iex> SendGmail.connect
'220 smtp.gmail.com ESMTP x10sm38834564pas.37 - gsmtp\r\n'
'250 smtp.gmail.com at your service\r\n'
'334 VXNlcm5hbWU6\r\n'
'334 UGFzc3dvcmQ6\r\n'
'235 2.7.0 Accepted\r\n'
'250 2.1.0 OK x10sm38834564pas.37 - gsmtp\r\n'
'250 2.1.5 OK x10sm38834564pas.37 - gsmtp\r\n'
'354  Go ahead x10sm38834564pas.37 - gsmtp\r\n'
'250 2.0.0 OK 1455543743 x10sm38834564pas.37 - gsmtp\r\n'
'221 2.0.0 closing connection x10sm38834564pas.37 - gsmtp\r\n'
:ok
実行結果は前回と変わりませんね。(変わったら困りますけど…)
さて、このままだといささか汎用性が足りな…全くありませんので、
gen_smtp_client(Erlang)を参考にして少し汎用性のあるコードに書き換えることと、
Elixir側で同一の機能が提供されているものがあれば、そちらの機能を使うように修正します。
今日はここまで、上記の改善案は明日以降に実施する。
(今のままだと日本語は扱えません…)
しかしこれ、改善していくと自然とMailmanと同じようなものになる気がする…(気のせい?)

Bibliography

人気の投稿