やーまんぶろぐ

気が向いた時にだけ書くブログ

Nginx Luaでmailを送信する方法

Nginx Luaでそのままmailを送信すると、lua coroutine: runtime error: attempt to yield across C-call boundary というエラーが出てしまいます。
このエラーを解消してメールを送信する方法をメモしておきます。

下のリンクの4番目のコメントを参考にしました。
stackoverflow.com

smtp.send uses LuaSocket's socket.protect function for handling internal errors. This function is implemented in C and doesn't allow yielding in the current releases (the version in git HEAD now allows yielding on Lua 5.2+, see discussion here). Apparently someone tries to yield from within it. In etc/dispatch.lua in the LuaSocket package (better use the git HEAD version) there is a replacement function for socket.protect that should allow yielding on all Lua versions (at the cost of an extra temporary coroutine). You can try replacing the C function with that Lua function like so:

attempt to yield across C-call boundaryエラーが出るコード

まずはエラーが出るコードをのせます。

local smtp = require("socket.smtp")
local mime = require("mime")

local function headerencode(src)
  return "=?UTF-8?B?" .. mime.b64(src) .. "?="
end


from = "<fromアドレス>"

rcpt = {
  "<toアドレス>"
}

mesgt = {
  headers = {
    from = headerencode("日本語名前") .. " <fromアドレス>",
    to = " <toアドレス>",
    subject = headerencode("日本語タイトル"),
    ["content-type"] = 'text/plain; charset="utf-8"'
  },
  body = "日本語ボディ"
}

r, e = smtp.send{
  from = from,
  rcpt = rcpt,
  source = smtp.message(mesgt),
  server = 'localhost',
}

これをNginx Luaで呼び出すと下のエラーが出ます。

lua coroutine: runtime error: attempt to yield across C-call boundary

Nginxを使わないでLuaのみで呼び出すとエラーは出ません。

attempt to yield across C-call boundaryエラー内容

続いてエラーの内容を見ていきます。

lua coroutine: runtime error はコルーチン(マルチスレッド的な意味)でエラーが出ていますよ。ということ。
明示しなくてもNginxでsmtp.sendを呼び出すとコルーチンで呼ばれるようです。

attempt to yield across C-call boundaryというのはC言語で書かれた部分でエラーが出ていますよ。ということ。
なので大元の.soファイルを修正するか、呼び出し方を変える必要があります。

参考にしたリンク先では、呼び出し方を変えています。

下のコードでprotectを上書きしましょうということで、この通りにコピペして使用したところエラーが解消されました。
github.com

attempt to yield across C-call boundaryエラー解消コード

エラー解消したコードをのせておきます。

local socket = require("socket")
local base = _G

if string.sub(base._VERSION, -3) == "5.1" then
  local function _protect(co, status, ...)
    if not status then
      local msg = ...
      if base.type(msg) == 'table' then
        return nil, msg[1]
      else
        base.error(msg, 0)
      end
    end
    if coroutine.status(co) == "suspended" then
      return _protect(co, coroutine.resume(co, coroutine.yield(...)))
    else
      return ...
    end
  end

  function socket.protect(f)
    return function(...)
      local co = coroutine.create(f)
      return _protect(co, coroutine.resume(co, ...))
    end
  end
end

local smtp = require("socket.smtp")
local mime = require("mime")

local function headerencode(src)
  return "=?UTF-8?B?" .. mime.b64(src) .. "?="
end


from = "<fromアドレス>"

rcpt = {
  "<toアドレス>"
}

mesgt = {
  headers = {
    from = headerencode("日本語名前") .. " <fromアドレス>",
    to = " <toアドレス>",
    subject = headerencode("日本語タイトル"),
    ["content-type"] = 'text/plain; charset="utf-8"'
  },
  body = "日本語ボディ"
}

r, e = smtp.send{
  from = from,
  rcpt = rcpt,
  source = smtp.message(mesgt),
  server = 'localhost',
}

最後に

これでNginx Luaでmailを出せました。
過去に書いたSendGridの記事と合わせると、SendGridで受信したmailをLuaで送信するようなことができそうです。

yamano3201.hatenablog.jp
yamano3201.hatenablog.jp

気が向いたら、また書きます。