ApacheSparkの基本的なデータ操作
データ操作
Spark2.1で動作確認
build.sbt
IDEAから実行できるようにbuild.sbtに以下を追加。対象のライブラリが存在しない場合はmaven_centralで確認する。
val sparkVersion = "2.1.0" libraryDependencies ++= Seq( "org.apache.spark" % "spark-core_2.11" % sparkVersion % "compile", "org.apache.spark" % "spark-sql_2.11" % sparkVersion % "compile" )
RDD
実行時の設定の読み込み
val conf = new SparkConf().setAppName("WordCountTop3").setMaster("local[*]") val sc = new SparkContext(conf)
ハイフン区切りで単語出現数のカウント
val filePath = "build.sbt" val wordAndCountRDD = sc.textFile(filePath) .flatMap(line => line.split(" ")) .map(word => (word, 1)) .reduceByKey(_ + _) wordAndCountRDD.collect.foreach(println)
単語出現数をソートして上位3件を表示
val filePath = "build.sbt" val wordAndCountRDD = sc.textFile(filePath) .flatMap(line => line.split(" ")) .map(word => (word, 1)) .reduceByKey(_ + _) wordAndCountRDD.sortBy( _._2.toInt, ascending = false).take(3).foreach(println)
DataFrame
設定の読み込み
val conf = new SparkConf().setAppName("WordCount").setMaster("local[*]") val sc = new SparkContext(conf) // spark2からはSparkSessioのほうが推奨だけどエラーで使えなかった val sqlContext = new org.apache.spark.sql.SQLContext(sc)
Spark2.1ではSQLContextではなくSparkSessionの方を推奨しているけど、エラーが発生して利用できなかった。とりあえずSQLContextで試してみる。
RDDでファイルを読み込む
val dessertRDD = sc.textFile("data/dessert-menu.csv") .map{ record => val splitRecord = record.split(",") val menuId = splitRecord(0) val name = splitRecord(1) val price = splitRecord(2).toInt val kcal = splitRecord(3).toInt Dessert(menuId, name, price, kcal) }
RDDからDataFrameへ変換
// RDD → DataFrame val dessertDF:DataFrame = sqlContext.createDataFrame(dessertRDD)
スキーマ定義の表示
println("show schema") println(dessertDF.printSchema)
DataFrameに対してクエリを投げる。生成ずみのDataFrameに対して直接クエリを投げるのではなくワークテーブルを作成して、そちらに対してクエリを実行する。ATAN2などの組み込み関数については公式のドキュメントを確認する。
dessertDF.createOrReplaceTempView("dessert") val numOver300KcalDF = sqlContext.sql( "select count(*) as num_of_over_300Kcal FROM dessert where kcal >= 260" ) println(numOver300KcalDF.show) println(sqlContext.sql("select atan2(1, 3) as `ATAN2(1, 3)`").show)
DataFrameをjoinしてクエリを投げる。
val dessertOrderRDD = sc.textFile("data/dessert-order.csv") .map{record => val splitRecord = record.split(",") val sId = splitRecord(0) val menuId = splitRecord(1) val num = splitRecord(2).toInt DessertOrder(sId, menuId, num) } val dessertOrderDF:DataFrame = sqlContext.createDataFrame(dessertOrderRDD) dessertOrderDF.createOrReplaceTempView("desert_order") println(sqlContext.sql( """ |select do.sId | , d.name | , do.num * d.price as amout_per_menu_per_slip |from desert d |join desert_order do on d.menuId = do.menuId """.stripMargin).show
Apache Sparkを触ってみた
Apache Sparkとは?
Hadoopと同じく分散処理のフレームワーク。HadoopではMapReduceと言って複数マシンで分散処理を行ってから結果をストレージに書き出す。1回の処理では終わらない場合はデータの処理フローを形成することになり、よみ出し→分散処理→書き込みを繰り返す動きをする。MapReduceでは処理の中間結果が常にストレージに書き出されるためデータ量が大きくなっても動作し、障害からの回復も容易であると言ったメリットがある。
しかしこれではあるデータの部分集合に対し複数回で処理する場合、都度すべてのデータをストレージに書き込む処理が行われるため必要な計算コストが大きくなってしまう。
Sparkでは連続する処理の中で無駄なディスクやネットワークのI/Oを起こさないように処理することでMapReduceの問題に対処している。Sparkの高速化が期待できるのは、複数回の反復処理や連続する変換処理になります。
Sparkのデータ構造
ApacheSparkのデータ処理には「RDD」と呼ばれるデータ構造を利用する。RDDは複数のマシンから構成されるクラスタ上での分散処理を前提として設計されており、内部的にはパーティションという塊に分割される。RDDをパーティションごとに複数のマシンで処理することで分散処理が行われる。
Sparkの分散処理環境
RDDのデータ構造を分散処理するためのクラスタ管理システムには以下のようなものがある。
* YARN
Hadoopのクラスタ管理システム。Hadoopの分散ファイルシステムであるHDFSで利用するとI/Oが効率化されるらしい。
* Mesos
* Spark Standalone
Sparkに同梱されているクラスタ管理システム。別途クラスタ管理システムを用意する必要がなく手軽に利用できる。
RDDによるタスク処理
RDDには以下の情報が含まれている
* RDDの元になるデータの情報、または変換前のRDD
* RDD成の元になるデータのロード方法、変換方法
* 変換後のRDDの要素の型
* 変換後のRDDのパーティション数
* RDDが永続化されているかどうか
RDD内に含まれる上記の情報と遅延評価の性質により効率的なタスクのスケジューリングが可能となっているらしいけど詳しくは別でドキュメントを見たほうがよさそう。
http://kimutansk.hatenablog.com/entry/20130902/1378142510
https://www2.eecs.berkeley.edu/Pubs/TechRpts/2012/EECS-2012-259.pdf
Sparkのマシン構成
ホスト名 | 役割 | 説明 |
---|---|---|
spark-client | クライアント | アプリケーションを起動するクライアント |
spark-master | マスターノード | クラスタ内のリソースを管理 |
spark-worker00 | ワーカーノード | 働く |
Sparkインストール
とりあえずmacにインストールしてみる
以下のページからsparkをダウンロードして解凍後binにパスを通すととりあえず動きを試すことができる
http://spark.apache.org/downloads.html
パスを通した後は以下のコマンドが実行できたらApacheSparkはインストールされている
spark-shell –version
IDEAでの開発
- ideaでSBTプロジェクトを作成
使用するscalaのバージョンは公式ページでapiをサポートしているバージョンを事前に確認しておく(2017/3/20時点ではscala2.12からは利用できなかった)
http://spark.apache.org/docs/latest/ - 実行可能jarが生成できるようにsbt-assemblyをプラグインに追加。追加方法は以下のgithubのページを参考にする。
https://github.com/sbt/sbt-assembly
plugins.sbtに以下を追加する
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.4")
org.jboss.interceptorがunresolvedになったらplugins.sbtにさらに以下を追記
resolvers += "JBoss" at "https://repository.jboss.org"
- build.sbtのlibraryDependenciesにspark-coreを追加する。
バージョンには気をつける
libraryDependencies ++= Seq( "org.apache.spark" % "spark-core_2.11" % "2.1.0" )
- abt-assemblyのオプションをbuild.sbtに追加
詳しくは以下のペジを参照。ファイル読み込み時に複数ファイルが見つかった時どうするかのために必要
https://github.com/sbt/sbt-assemblyassemblyMergeStrategy in assembly := { case PathList("javax", "servlet", xs @ _*) => MergeStrategy.first case PathList(ps @ _*) if ps.last endsWith ".html" => MergeStrategy.first case "application.conf" => MergeStrategy.concat case "unwanted.txt" => MergeStrategy.discard case x => val oldStrategy = (assemblyMergeStrategy in assembly).value oldStrategy(x) }
ついでに出力するjarファイル名も設定しておくassemblyJarName in assembly := "something.jar"
- 試しにjar出力
ApacheSparkで実行可能なjarを試しに出力しておく > sbt assembly で多分失敗するので、その場合はlibraryDependenciesをprovidedにしておく。"org.apache.spark" % "spark-core_2.11" % "2.1.0" % "provided"
それから再度sbt assemlbyを実行して所定のパスにjarが生成されることを確認。ただprovidedの場合有効なスコープがコンパイル時とテスト時になる。今回はIDEAから直接実行して簡単に動きを見るだけなので一旦以下のように変更しておく。"org.apache.spark" % "spark-core_2.11" % "2.1.0" % "compile"
簡単なスクリプトをIDEAから動かしてみる
それではSparkを実行するsbtプロジェクトが準備できたので試しにサンプルコードを動かしてみます。 以下のページにある単語の出現数をカウントするscalaのプログラムを動かしてみたいと思います。 http://spark.apache.org/examples.html
import org.apache.spark.{SparkConf, SparkContext} object spark_shell { def main(args:Array[String]): Unit ={ val conf = new SparkConf().setAppName("WordCount") val sc = new SparkContext(conf) try{ val filePath = "build.sbt" val wordAndCountRDD = sc.textFile(filePath) .flatMap(line => line.split(" ")) .map(word => (word, 1)) .reduceByKey(_ + _) wordAndCountRDD.collect.foreach(println) } sc.stop() } }
まあ、単純に読み込んだファイルをスペース区切りで分割してwordごとのmapでカウントアップするだけです。
これで実行してみると以下のエラーが発生すると思います。
- SparkException: A master URL must be set in your configuration
このエラーについてですが、sparkのタスクを実行する場合spark-clientからspark-masterを指定してプログラム実行の流れになるのですが、IDEAでそのまま実行するだけではmaster未指定のため上記エラーが発生すると思われます。本番環境のクラスタリング構成で動かす場合はpark-submitのオプションで指定するのかと思います。 http://spark.apache.org/docs/latest/submitting-applications.html
今回は手軽に動きを試したいのでプログラム中のSparkConfを以下のように変更しmasterとして自分自身を指定して動きを見たいと思います。
val conf = new SparkConf().setAppName("WordCount").setMaster("local[*]")
それから実行してみると環境情報等読み込んだ後に以下のような出力結果が得られると思います。
(oldStrategy,1) (x,1) (endsWith,1) ("2.11.8",1) (assembly).value,1) (scalaVersion,1) (,97) (@,2) ("unwanted.txt",1) (libraryDependencies,1) ("spark-core_2.11",1) ("2.9.7",1) ("joda-time",2) (PathList(ps,1) (_*),2) (assembly,3)
Scalaで数式をパースして整形表示
数式パーサ
Scalaのコップ本を一通り読んだので振り返りも兼ねて数式をパースして計算、整形するDSLを実装したいと思います。全部自力で作れたら良いのですが、ソースコードはほとんど(http://blog.j5ik2o.me/entry/20101126/1290763057) とコップ本から拝借しています。
パースした数式は探索して評価を行うので、式を表すExpressionトレイトと訪問するExpressionVisitorトレイトを用意します。
// 式を訪問するビジター trait ExpressionVisitor { def visit(e:Expression):BigDecimal } // 式を表すトレイト trait Expression { def accept(visitor:ExpressionVisitor):BigDecimal = { visitor.visit(this) } }
それから各数式を表すのに使うケースクラスを定義しておきます。
// 各式を表すケースクラス。これを使ってAST(抽象構文木)を作ります。 case class Value(digit:BigDecimal) extends Expression case class Add(expr1:Expression, expr2:Expression) extends Expression case class Sub(expr1:Expression, expr2:Expression) extends Expression case class Plus(expr:Expression) extends Expression case class Minus(expr:Expression) extends Expression // カッコで囲んでいる部分 case class Parenthesized(expr:Expression) extends Expression case class Multiply(expr1:Expression, expr2:Expression) extends Expression case class Divide(expr1:Expression, expr2:Expression) extends Expression // 数式と評価結果を表す case class ExecResult(exp:Expression, result: Value) extends Expression
RegexParsersを使って数式をパースします。構文解析にはASTというものを使います。数式のクラスは入れ子構造になるのですが、ASTで解析する時はまず+,-次に*,/それからカッコというように優先度の低いものを先に評価することで正しくパースが行えるようです。この辺りは既にあるものを見るならなんとなくわかるけど、自分で一から考えるのは難しそう
// 式のパーサ class ExprParser extends RegexParsers { def parse(data:String) = parseAll(expression, data) // expression ::= term { "+" term | "-" term } def expression : Parser[Expression] = term~rep("+"~term | "-"~term) ^^ { case opr1~lists => { var operand1 = opr1 lists.foreach { l => l match { case "+"~f => { operand1 = Add(operand1, f) } case "-"~f => { operand1 = Sub(operand1, f) } } } operand1 } } // term ::= factor { "*" factor | "/" factor } def term : Parser[Expression] = factor~rep("*"~factor | "/"~factor) ^^ { case opr1~lists => { var operand1 = opr1 lists.foreach { l => l match { case "*"~f => { operand1 = Multiply(operand1, f) } case "/"~f => { operand1 = Divide(operand1, f) } } } operand1 } } // factor ::= unary def factor : Parser[Expression] = unary // unary :: = "+" unary | "-" unary | primary def unary : Parser[Expression] = ("+"|"-")~unary ^^ { case "+"~u => Plus(u) case "-"~u => Minus(u) }| primary // primary ::= "(" expression ")" | value def primary : Parser[Expression] = "("~expression~")" ^^ { case lbrace~expr~rbrace => Parenthesized(expr) }|value // value ::= "[0-9]+" // floatingPointNumberは浮動小数点を表す正規表現のParser def value : Parser[Expression] = fpn ^^ { n => Value(BigDecimal(n, MathContext.DECIMAL32)) } def fpn: Parser[String] = """-?(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r }
それから、パース後に数式の答えを出すのは以下のvisitorパターンを使います。
class Evaluator extends ExpressionVisitor { // ケースクラスの抽出(unapply)を使って内包されている式を取り出してさらに訪問する override def visit(e:Expression) : BigDecimal = e match { case Value(digit) => digit case Add(l,r) => l.accept(this) + r.accept(this) case Sub(l,r) => l.accept(this) - r.accept(this) case Multiply(l,r) => l.accept(this) * r.accept(this) case Divide(l,r) => l.accept(this) / r.accept(this) case Minus(e) => e.accept(this) * -1 case Plus(e) => e.accept(this) case Parenthesized(e) => e.accept(this) } }
整形表示なしで式を計算するだけなら、以下のようになります。
val parser = new ExprParser val parseResult = parser.parse("3/2 + 5 * (24 + 7) /2") if ( parseResult.successful ){ parseResult.get.accept(new Evaluator) }else{ "" }
次に、数式の整形表示についてですが、表示用の情報はElementオブジェクトを拡張したクラスを使用して使います。
object Element{ private class ArrayElement(val contents: Array[String]) extends Element private class LineElement(s: String) extends Element{ val contents = Array(s) override def width = s.length override def height = 1 } private class UniformElement( ch:Char, override val width: Int, override val height:Int )extends Element{ private val line = ch.toString * width def contents = Array.fill(height)(line) } def elem(contents:Array[String]):Element = new ArrayElement(contents) def elem(chr: Char, width: Int, height: Int ): Element = new UniformElement(chr, width, height) def elem(line: String): Element = new LineElement(line) }
表示用のクラスには数式の一部が格納されていて、その一部の数式同士の位置を調整するためのメソッド群をElementのabstractクラスに持たせておきます。分子を分母の上に表示するのにはここのクラスのメソッドを使用します。
abstract class Element { import util.Element.elem def contents: Array[String] def width: Int = contents(0).length def height: Int = contents.length def above(that: Element): Element = { val this1 = this widen that.width val that1 = that widen this.width assert(this1.width == that1.width) elem(this1.contents ++ that1.contents) } def beside(that: Element): Element = { val this1 = this heighten that.height val that1 = that heighten this.height elem( for((line1, line2) <- this1.contents zip that1.contents) yield line1 + line2) } def widen(w:Int):Element = if(w<=width) this else{ val left = elem(' ', (w-width)/2, height) val right = elem(' ', w - width - left.width, height) left beside this beside right } def heighten(h: Int): Element = if(h <= height)this else{ val top = elem(' ', width, (h - height)/2) val bot = elem(' ', width, h - height - top.height) top above this above bot } override def toString = contents mkString "\n" }
それからパースされた数式のクラスを表示用クラスに変換するExprFormatterクラスを作成します。Scalaのパターンマッチを使って入れ子構造のクラスを再帰的に評価しています。
class ExprFormatter { private val fractionPrecedence = -1 private def format(e: Expression, enclPrec: Int): Element = e match { case Value(num) => elem(" " + num.toString + " ") case Add(x, y) => val l = format(x, fractionPrecedence) val op = elem("+") val r = format(y, fractionPrecedence) l beside op beside r case Sub(exp1, exp2) => val l = format(exp1, fractionPrecedence) val op = elem("-") val r = format(exp2, fractionPrecedence) l beside op beside r case Plus(exp) => val op = elem("+") val ex = format(exp, fractionPrecedence) op beside ex case Minus(exp) => val op = elem("-") val ex = format(exp, fractionPrecedence) op beside ex case Parenthesized(exp) => val l = elem("(") val ex = format(exp) val r = elem(")") l beside ex beside r case Multiply(exp1, exp2) => val l = format(exp1, fractionPrecedence) val op = elem("x") val r = format(exp2, fractionPrecedence) l beside op beside r case Divide(exp1, exp2) => val top = format(exp1, fractionPrecedence) val bot = format(exp2, fractionPrecedence) val line = elem('-', top.width max bot.width, 1) val frac = top above line above bot if (enclPrec != fractionPrecedence) frac else frac case ExecResult(exp, result) => val e = format(exp, fractionPrecedence) val eq = elem("=") val r = format(result, fractionPrecedence) e beside eq beside r } def format(e: Expression): Element = format(e, 0) }
最後に数式のパース,整形表示は以下のように利用します。
trait MathParser { def mathParse(input: String) = { val f: ExprFormatter = new ExprFormatter val parser = new ExprParser val parseResult = parser.parse(input) if ( parseResult.successful ){ val evalResult = parseResult.get.accept(new Evaluator) f.format(ExecResult(parseResult.get, Value(evalResult))) }else{ "" } } }
これで以下のように入力を行うと
3/2 + 5 * (24 + 7) /2
こんな風に整形して出力されることが確認できます。
3 5 x( 24 + 7 ) ---+--------------= 79.0 2 2
他の言語でDSLを実装したことがないので比較はできませんが、Scalaを使うことでASTで表した構造をそのままコードに落とし込むイメージがつきやすい気がしたのでパーサの構築の敷居が大分下がっているように思いました。
Postgreyについて
Postgrey
Postgreyとはpostfixを使用する時に初めて来る相手からは一定の待ち時間後に再送が来なければ受信を受け付けないといったもので、phpとかで大量のスパムメールを送りつけてきたのを受診させないためのものになっています。 インストールから使用するまでを簡単にまとめると以下のようになります。
インストール〜使用開始まで(CentOS7系で確認)
# yum install epel-release
# vi /etc/yum.repos.d/epel.repo
enabled=0
# yum –enablerepo=epel install postgrey
# vi /etc/sysconfig/postgrey
POSTGREY_OPTS="--inet=10023 --delay=1800" #メインサーバの設定にあわせる
○postgrey起動
# systemctl start postgrey
# systemctl enable postgrey
グレイリスティングの対象について
上記設定を行うと初めてくる相手からは以下のようにログが出力されるようになっています。
Sep 11 07:52:43 xxx postgrey[31898]: action=greylist, reason=new, client_name=xxx, client_address=xxx, sender=xxx, recipient=xxx
“action=greylist, reason=new"となっていますが、これは初めてメールを送ってきた相手のため一旦受診を拒否したといった内容になっております。ここでいう初めて送ってきた相手についてですが、最近まではメールサーバ間でのやり取りがあったかどうかだと思っていたのですが実際は違っていたようでして、それが確認できるログは以下のようになっています。
Sep 11 08:01:05 xxx postgrey[31898]: action=pass, reason=triplet found, delay=508, client_name=xxx, client_address=xxx, sender=xxx, recipient=xxx
これはPostgreyのデータベース内にtriplet(送信元サーバIP, 送信者アドレス、 受信者アドレスの組み合わせ)が存在したため、グレイリスティングにパスしたということを表しています。自分はこのログを見て気づいたのですがどうやら送信アドレス、受信者アドレス、送信サーバ毎で待ち時間が発生するようです。
Dovecotでメール削除をログ出力させる
削除ログを出力するようにする
dovecotのプラグインを有効にしメール削除をログ出力するように対応した際の手順です。
1.POPサーバの設定ファイルを変更しログ出力用のプラグインを有効化する
vi /etc/dovecot/conf.d/10-mail.conf
・変更前(210行目辺りです) #mail_plugins = ↓ ・変更後 mail_plugins = $mail_plugins mail_log notify
2.POPサーバのログ出力設定ファイルを変更し、メール削除を出力するようにする
vi /etc/dovecot/conf.d/10-logging.conf
・変更前(48行目辺りです) plugin { # Events to log. Also available: flag_change append #mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename # Available fields: uid, box, msgid, from, subject, size, vsize, flags # size and vsize are available only for expunge and copy events. #mail_log_fields = uid box msgid size } ↓ ・変更後(コメントアウトを外す) plugin { # Events to log. Also available: flag_change append mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename # Available fields: uid, box, msgid, from, subject, size, vsize, flags # size and vsize are available only for expunge and copy events. mail_log_fields = uid box msgid size }
3.POPサーバを再起動し、ログ出力の設定反映する
systemctl restart dovecot
以上の設定が完了したら/var/log/maillogに以下のようにゴミ箱に移した後に削除したといった内容のログが出力されるようになったはずです。
imap(user): copy -> Trash: uid=908, msgid=<123.foo@bar> imap(user): delete: uid=908, msgid=<123.foo@bar> imap(user): expunged: uid=908, msgid=<123.foo@bar>
skinny事始め
scalaのフレームワークで簡単にとっつきやすそうのを探したらskinnyが良さそうだったので、触り始めた際のメモを残しておきたいと思います。
skinnyインストール(mac)
プロジェクト作成から起動
$ skinny new プロジェクト名 $ cd プロジェクト名 $ ./skinny run
ブラウザから'http://localhost:8080/‘にアクセスしてページが表示されることを確認
ビルド
jetty内臓のjar生成
$ ./skinny package:standalone
デフォルト設定ではstandalone-build/target/scala-x.xxに実行可能jarが出力されることを確認済み
起動
$ ./skinny run
起動スクリプトにてjettyをcontainer:restartしているのを確認
デバッグ
$ ./skinny debug
sbt debugで5005ポート指定でjettyを起動しておりideaから5005ポート指定でリモートでバックできるのを確認。
開発
コントローラー
package controller import skinny._ class RootController extends ApplicationController { def index = { set("name", "jjug") render("/root/index") } }
set(“name”, “jjug”)の部分でリクエストスコープにkey=nameでjjugをセットしている。renderではsspのページを指定している。sspはscalateのテンプレートエンジンで以下を参考にする。 http://scalate.github.io/scalate/documentation/ssp-reference.html#syntax
<%@val name: String%> <h3>Hello ${name}!</h3> <hr/> <p> Your first Skinny app works! </p>
上記のようなsspであればリクエストスコープせセットしたnameがApplicationControllerで指定したjjugに置換して表示される。
ルート
skinnyでURLからメソッドを呼び出す場合、ApplicationControllerを拡張したクラス内にメソッドを追加しておく。
class RootController extends ApplicationController { def index = { set("name", "jjug") render("/root/index") } def ssp_test = { set("name", "jjug") render("/root/ssp_test") } }
それから、サーブレットコンテキストに紐付ける。
package controller import skinny._ import skinny.controller.AssetsController object Controllers { def mount(ctx: ServletContext): Unit = { root.mount(ctx) AssetsController.mount(ctx) } object root extends RootController with Routes { val indexUrl = get("/")(index).as('index) val sspTestUrl = get("/ssp_test/?")(ssp_test)as('ssp_test) } }
上記の例ではURLとメソッドをマッピングさせたrootオブジェクトをroot.mount(ctx)でサーブレットのコンテキストに追加している。get(“/ssp_test/?”)(ssp_test)as(‘ssp_test)の最初のssp_testの方はメソッドの実態でas('ssp_test)の部分はbeforeAction,afterActionで必要になるようで詳しくは以下を参照する。 http://skinny-framework.org/documentation/controller-and-routes.html
Jsonレスポンスを返す
spray-josnでjson形式のレスポンスを返してみる。一応skinny自身のutilにもjsonのパーサーは入っているのだが、とりあえずspray-jsonの方で試してみる。 まずbuild.sbtに以下を追記する
libraryDependencies += "io.spray" %% "spray-json" % "1.3.1"
それから暗黙の型変換で戻り値の形式を指定した上で以下のようにjsonレスポンスを返してみる。
class RootController extends ApplicationController { implicit object AnyJsonFormat extends JsonFormat[Any] { def write(x: Any) = x match { case n: Int => JsNumber(n) case s: String => JsString(s) case b: Boolean if b == true => JsTrue case b: Boolean if b == false => JsFalse } def read(value: JsValue) = value match { case JsNumber(n) => n.intValue() case JsString(s) => s case JsTrue => true case JsFalse => false } } def index = { set("name", "jjug") render("/root/index") } def ssp_test = { contentType = formats("json") val map = Map("japan" -> "tokyo", "america" -> "chicago", "denmark" -> "kopenhagen") map.toJson } }
vue.js使用時にvue-resourceでhttpリクエストを投げてみる
vue-resourceでhttpリクエストを投げてみた
公式では以下のようにvue-resourceは公式推奨からは外すけど、ajaxはVue自身が管理すべき領域でないからということで、使うべきではないとかそういった意味合いではないようだ。
https://jp.vuejs.org/2016/11/03/retiring-vue-resource/
インストール
$ npm install vue-resource
試してみる
jsファイル内に以下の追記を行いvue-resourceを有効化しておく
import VueResource from 'vue-resource' Vue.use(VueResource)
それからgetリクエストを投げる場合は、以下のようになる。
this.$http.get(リクエストURL').then( response => { console.info(response.body) }, response => { //エラー時の処理 }) }
簡単に動作確認はできた。
Vueの公式ではhttpリクエスト周りは自分で好きなの選んでということだったが、とりあえずはvue-resourceを使っとけば良いのかと思います。