Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

AWS LambdaでCommon Lispの任意の関数を動かせるようにした

blog.3qe.us

これでAWS Lambdaで動作するCommon Lispランタイムを実装したが,これを強化して,ハンドラを介してシステム内にある任意の関数を呼び出せるようにしたので紹介する.

f:id:Windymelt:20190207203617p:plain
ハンドラに完全修飾した関数名を指定できる

この機能もリポジトリに入れてある.

github.com

また,この機能の実装は以下を参考にした.

[http://y2q-actionman.hatenablog.com/entry/2018/12/06/AWS_Lambda%E3%81%AE_Custom_Runtime%E3%81%A8%E3%81%97%E3%81%A6_Common_Lisp%28sbcl%29%E3%82%92%E4%BD%BF%E3%81%86:title]

関数を指定する

ハンドラで関数を指定するには,完全修飾した関数名をハンドラに与える.例えばfooパッケージのhoge関数を指定するにはfoo:hogeと記述する.非公開の関数を指定するにはfoo::hogeのように指定する.

関数にはLambdaにリクエストしたbodyが引数として渡ってくる.これを処理して文字列を返すと,Lambdaのリクエストに値を返すことができる.

上掲のスクショではCL-USER:IDENTITY(引数をそのまま返す標準関数)を指定し,そのままリクエストしたbodyを返している.

f:id:Windymelt:20190207204638p:plain
渡したbodyがそのまま返る

AWS LambdaでCommon Lispを実行し,ロードされたシステムの任意の関数を呼び出せるようになった.

実装

主要な部分は以下の通り.

(defun handler->function (handler-string)
  "Returns function object designated by HANDLER-STRING, formatted in \"PACKAGE:SYMBOL\" form.
 Package name cannot be omitted."
  (let* ((split (multiple-value-list (cl-ppcre:scan-to-strings "([^:]+)::?(.+)" handler-string)))
         (pkg-name (string-upcase (elt (cadr split) 0)))
         (func-name (string-upcase (elt (cadr split) 1)))
         (pkg (find-package pkg-name)))
    (if pkg
        (find-symbol func-name (find-package pkg))
        nil)))

(defun default-handler (content)
  "Only returns message."
  (declare (ignorable content))
  "Handler not found.")

(defun get-handler (handler)
  "Returns function object from string using HANDLER->FUNCTION.
 When symbol not found or error occurred, DEFAULT-HANDLER is returned."
  (handler-case
      (or (handler->function handler)
          #'default-handler)
    (error () #'default-handler)))

(defun main-loop (handler)
  "Main loop processes lambda requests. Specify function designator with HANDLER."
  (declare (ignorable handler))
  (let ((next-endpoint
          (format nil "http://~A/2018-06-01/runtime/invocation/next" *aws-lambda-runtime-api*)))
    (loop for (body status headers . nil) = (multiple-value-list (dex:get next-endpoint))
          as request-id = (gethash "lambda-runtime-aws-request-id" headers)
          as response = (funcall (get-handler handler) body)
          as response-endpoint = (format nil "http://~A/2018-06-01/runtime/invocation/~A/response" *aws-lambda-runtime-api* request-id)
          do (dex:post response-endpoint :content response))))

今後の展望

  • status codeを関数に渡せるようにする?
  • 成功させず関数がエラーを返せるようにする?
  • 既存のシステムをLambda化するラッパーを実装する?
★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?