AWS LambdaでHTML→XML→JSON変換をやってみた


「AWSLambdaがなんかよいらしい」、ということをこの前勉強会で聞いたので試そうと思ったのがきっかけで、ついでにQiita初投稿

AWS自体初めてで、サーバーレスアーキテクチャだとかマイクロサービスだとかも全然理解できていないので変なところがあったら突っ込み歓迎です。

#やりたかったこと
- AWS Lambdaの使い方を覚える
- 汎用的に使えそうななにかを作ってみる

#やったこと
WebAPIと聞いて最初の思いついたのがデータフォーマットのコンバーターだったのでXMLからJSON変換をやってみようと思った。
↓
データ作るのも面倒なのでその辺にころがってるHTMLをXMLに変換してそれをJSONに変えてみようと思い立つ
↓
Lambdaという名前から安直に高階関数をイメージしたので

```
HTMLtoXML = (text/htmlを返すURL) ー> application/xml
XMLtoJson = (application/xmlを返すURL) ー> application/json
XMLtoJson(HTMLtoXML(HTMLを返すURL)) → JSon化されたHTML
```
といった仕様のラムダAPIを作ってみた

#まずは
AWSの契約
1年無料らしいのでとりあえず登録
![AWS.GIF](https://qiita-image-store.s3.amazonaws.com/0/35910/7565658c-8e8c-fe4d-8b56-ba6b22570c8c.gif)
サービスいっぱいあってすごい

#AWS Lambda
2016年8月時点、function実装に使える言語はNode.js、Python、Javaとのこと
Nodeだと簡単そうだったけどせっかくなので普段使ってるScalaでやってみることにした

### functionの実装
たいしたことしてないので全ソースのせる
(https://github.com/bakenezumi/aws-converter)

AWSLambdaは1jar(fat jar)でデプロイしないといけないので```activator assembly```でfat jarが作れるプラグインを追加

```scala:project/plugins.sbt
resolvers += "sbt-assembly-resolver-0" at "http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0")
```

build.sbtにAWS関係やパース関係のライブラリを追加
（実際はいろいろ試したけど最終形）

```scala:build.sbt
name := """aws-converter"""

version := "1.0"

scalaVersion := "2.11.7"

libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.4" % "test"

libraryDependencies += "com.amazonaws" % "aws-lambda-java-core" % "1.0.0"

libraryDependencies += "org.scala-lang.modules" %% "scala-xml" %  "1.0.1"

libraryDependencies += "nu.validator.htmlparser" % "htmlparser" % "1.4"

libraryDependencies += "net.liftweb" %% "lift-json" % "2.6+"

```

functionはこんなかんじ
ほぼLiftからもってきた

```scala:src/main/scala/Converter.scala
package converter

trait ConverterBase {
  import com.amazonaws.services.lambda.runtime.Context
  import io.Source

  // テスト時はオーバーライドできるように（ファイルからとかプロキシ使うときとか）
  def fromURL(url: String, encode: String = "UTF-8") = Source.fromURL(url, encode)

  def htmlToXml(url: String, context: Context) = {
    import java.io.StringReader
    import scala.collection.JavaConverters._
    import scala.xml.Node
    import scala.xml.parsing.NoBindingFactoryAdapter
    import nu.validator.htmlparser.sax.HtmlParser
    import nu.validator.htmlparser.common.XmlViolationPolicy
    import org.xml.sax.InputSource

    val hp = new HtmlParser
    hp.setNamePolicy(XmlViolationPolicy.ALLOW)
    val saxer = new NoBindingFactoryAdapter
    hp.setContentHandler(saxer)
    hp.parse(new InputSource(new StringReader(fromURL(url).getLines.mkString)))

    saxer.rootElem.toString

  }

  def xmlToJson(url: String, context: Context) = {
    import scala.xml.XML
    import net.liftweb.json.{Xml, pretty, render}
    val source = fromURL(url).getLines.mkString
    val xml = XML.loadString(source)
    pretty(render(Xml.toJson(xml)))
  }
}

class Converter extends ConverterBase
```

やる気のないテストコード

```scala:src/test/scala/ConverterSpec.scala
import org.scalatest._
import converter._

class ConverterSpec extends FlatSpec with Matchers with ConverterBase{
  import io.Source

  override def fromURL(url: String, encode: String = "UTF-8") = Source.fromFile(url, encode)

  "Converter.htmlToXml" should "ok" in {
    val url = "src/test/scala/test.html"
    val ret = htmlToXml(url, null)
    println(ret)
    ret != null
  }

  "Converter.xmlToJson" should "ok" in {
    val url = "src/test/scala/test.xml"
    val ret = xmlToJson(url, null)
    println(ret)
    ret != null
  }

}
```

おもむろにactivator assembly
target/scala-2.11/の下にaws-converter-assembly-1.0.jarができた！

###functionののデプロイ
コマンドでもできるらしいけどコンソールでやった
やり方は割愛

#Web公開（AWS API Gateway）
ここからが本題
AWSすごい
![Triger.GIF](https://qiita-image-store.s3.amazonaws.com/0/35910/173786f0-72d3-722c-64ad-b71d2641afb4.gif)
TriggersからAddtriggerを選ぶとかっこいい画面がでてくる
![addtrigger.GIF](https://qiita-image-store.s3.amazonaws.com/0/35910/c81a7aa3-f753-2178-f9e4-ee255e9627ab.gif)

左側にAPI Gatewayをえらぶ
![addtrigger.2GIF.GIF](https://qiita-image-store.s3.amazonaws.com/0/35910/8d4e4266-5e22-dac1-88dd-1663ada7b399.gif)

Lambda名と同一のResource名が初期表示される
SecurityだけOpenにしてSubmitするとひとまずOK


ただこのままだと動かない（LambdaにURLをGETで渡せない）ので、ごにょごにょする必要がある
API Gatewayの管理画面に飛んで

![Gateway1.GIF](https://qiita-image-store.s3.amazonaws.com/0/35910/a54c89cb-bcf3-ebff-552c-cae078c931ee.gif)

まずhtmlToXMLの「GETのメソッドリクエスト」の「URL クエリ文字列パラメータ」を追加
![Gateway2.GIF](https://qiita-image-store.s3.amazonaws.com/0/35910/2eff7d34-4209-ff05-8f12-faee2ce11861.gif)

次に「統合リクエスト」を下記編集
![Gateway3.GIF](https://qiita-image-store.s3.amazonaws.com/0/35910/58777685-dc67-1298-b9b2-a92d98cc6b80.gif)

こうすることでGETで渡したパラメータをラムダに渡すようになる
で、テストしてみると
![Gateway4.GIF](https://qiita-image-store.s3.amazonaws.com/0/35910/44df5478-1470-8dfa-d100-3d2f52fc18f3.gif)

成功！のようで実は失敗
AWS Lambdaでは返りがJSONなので、文字列を返した場合""で囲まれてしまう

なのでもう一個編集「統合レスポンス」

![image](https://qiita-image-store.s3.amazonaws.com/0/35910/15431584-07b7-884b-b315-33d79e7759ee.png)

レスポンスの```Content-Type```を```application/xml```にして、```$input.path('$')```というテンプレートに流すと生データがレスポンスされるようになる

![image](https://qiita-image-store.s3.amazonaws.com/0/35910/9c9a8de6-9e39-89d8-e10a-b3de37b493e5.png)

できた！
んでprodにデプロイするとサービス公開完了！
```https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/htmlToXml?url=http://yahoo.co.jp```
（hogehogeは内緒）にアクセスするとyahooがXMLになった！

同様にxmlToJsonを公開すると、、、
```https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/xmlToJson?url=https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/htmlToXml?url=http://yahoo.co.jp```

![image](https://qiita-image-store.s3.amazonaws.com/0/35910/25cae3aa-3d9e-b801-38b5-7d5ff4354332.png)

できた！！

#APIの統合（2016/8/8追記）
HTML→XML→JSONを一発でやってくれるAPIはいままで作ったAPIの組み合わせで実装できた

- API-Gatewayに/htmlToJsonのGETメソッドを作成（リンク先はxmlToJsonのラムダ）

- 「メソッドリクエスト」の「URLクエリ文字列パラメータ」に"url"を追加

- 「統合リクエスト」の「マッピングテンプレート」を```"https://hogehoge.execute-api.ap-northe1.ast-amazonaws.com/prod/htmlToXml?url=$input.params('url')"```に

 ![image](https://qiita-image-store.s3.amazonaws.com/0/35910/9d8eb81d-85a0-8265-4eeb-e5081094eb12.png)
    
- 「統合レスポンス」は今までと同様生データを返すようにする

prodにデプロイすると```https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/prod/htmlToJson?url=http://yahoo.co.jp```で期待した結果が得られた


#所感
基本的な使い方は理解できた
サーバー立てずにサービス公開できるって素敵
あとScalaは重かった（約20MByte）