Play Framework(Scala)の入門記事です。
(19/12/07 20:38追記)
作業環境をWindowsからLinux(Linux Mint)に変更しました。
そのため、既存のPowerShell箇所のコマンド・出力をBashに変更しました。
最近Play Frameworkを勉強し始めたので、アウトプットがてらやったことを書き記して行こうと思います。
今回はとりあえずひな形をインストールして、GET/POSTでアクセスできるようになるまでです。
#はじめに
##Play Frameworkとは
軽量・ステートレス・非同期処理が特徴の、ScalaおよびJava用のWebフレームワークです。
Play Framework - Build Modern & Scalable Web Apps with Java and Scala
##作りたいもの
Play Frameworkを使用し、簡単なWeb APIを作ります。
サーバーサイドレンダリングは行いません。
##想定対象読者
- Scalaの基本的な文法を理解している方。
- Webの基本的な用語(GET/POST、リクエストヘッダー、Content-Typeなど)を理解している方。
##前提条件
自身のマシンに以下のものをインストールしておく必要があります。
- JDK(1.8またはそれ以降)
- ビルドツール(sbt または Gradle)
##動作環境
以下の環境で検証(テスト)しています。
また動作環境はテストが通ることを確認でき次第、随時更新していきます。
- Linux Mint 19.3 Tricia 64bit (Cinnamon desktop)
- AdoptOpenJDK 11.0.7 HotSpot
- Scala 2.13.1
- sbt 1.3.8
- Play Framework 2.8.0
エディタはIntelliJ IDEAを使用していますが、できるだけIDEに依存しない書き方をするつもりです。
お好きなエディタを使ってください。
それと途中経過を(自分用に)保存するために、Gitで途中途中コミットしています。
なおOSがWindows10のため、コマンド類はPowerShellで実行しています。
MacやLinuxの方は適宜読み替えてください。
(19/12/07 追記) Linuxに変更したためbashの記述に書き換えました。
また、各ファイルの文字コードはUTF-8で統一します。
#インストール・初期化
##インストール
sbtを使い、Play Frameworkのひな形をインストールします。
便宜上、ホームディレクトリ配下のxxxフォルダにインストールします。
実際には好きなフォルダを使ってください。
インストール用のコマンドは以下のとおりです。
~/xxx$ sbt new playframework/play-scala-seed.g8
上記コマンドを実行すると、nameとorganizationを求められます。
nameはここではplay-api-sample
とします。
organizationは空欄のままEnterを押します(デフォルト値として「com.example」が設定されます)。
This template generates a Play Scala project
name [play-scala-seed]: play-api-sample
organization [com.example]:
- 補足
上記操作をWindowsのPowerShell上で行った場合、sbt 1.3.0以降ではnameへの入力値が表示されません(不具合?)。
入力自体はされているので、一通り入力してからEnterを押していただければ先に進めます。
なおWindowsの場合下記のようなエラーが出るようですが、調べた限りでは無視して良いようです。
参考: Play Frameworkハンズオン環境構築 - Qiita
[error] java.io.IOException: Unable to delete file: C:\Users\xxx\AppData\Local\Temp\giter8-***\src\main\g8\.gitignore
[error] at org.apache.commons.io.FileUtils.forceDelete(FileUtils.java:2400)
...
play-api-sampleフォルダができているので、そのまま移動します。
~/xxx$ cd play-api-sample/
~/xxx/play-api-sample$
以降、カレントディレクトリを表す~/xxx/play-api-sample
の部分は省略します。
フォルダ・ファイル構成としては以下のようになっています。
$ tree
.
├── app
│ ├── controllers
│ │ └── HomeController.scala
│ └── views
│ ├── index.scala.html
│ └── main.scala.html
├── build.sbt
├── conf
│ ├── application.conf
│ ├── logback.xml
│ ├── messages
│ └── routes
├── project
│ ├── build.properties
│ └── plugins.sbt
├── public
│ ├── images
│ │ └── favicon.png
│ ├── javascripts
│ │ └── main.js
│ └── stylesheets
│ └── main.css
└── test
└── controllers
└── HomeControllerSpec.scala
11 directories, 14 files
###主要なフォルダ・ファイル
上記のフォルダ・ファイルの内、押さえておきたいものだけ説明します。
下記を含めた全体像を見たい場合は 公式ドキュメント で確認してください。
-
build.sbt
: ビルドスクリプト -
app
: アプリケーションのソースフォルダ -
conf
: アプリケーションの設定ファイル用フォルダ-
application.conf
: スレッドプール、データベース、セキュリティなど、アプリケーションの基本的な設定ファイル -
routes
: HTTPメソッド、リクエストパスに応じたルーティング設定ファイル
-
-
project
: sbt 設定ファイル群-
build.properties
: 使用する sbt のバージョンを宣言する指標的なファイル -
plugins.sbt
: Play 自身の定義を含む sbt プラグイン
-
-
test
: 単体、および機能テスト用のソースフォルダ
##初回実行
一度この状態で起動してみます。
sbt run
コマンドを実行し、ブラウザでlocalhost:9000
にアクセスします。
$ sbt run
(中略)
--- (Running the application, auto-reloading is enabled) ---
[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Enter to stop and go back to the console...)
正常にアクセスできました。
「Ctrl + C
」でsbtを終了します。
この時点で一度コミットしておきます。
なお、コミットメッセージに日本語を使うと表記がずれる可能性がありますが、Gitに関しては今回の主題ではないため取り上げません。
$ git init
Initialized empty Git repository in /home/xxx/play-api-sample/.git/
$ git add .
$ git commit -m "初回インストール"
[master (root-commit) b5f3afb] 初回インストール
19 files changed, 305 insertions(+)
...
##インポート(※IntelliJ IDEAを使用する場合のみ)
IntelliJをお使いの方は、以下の手順でプロジェクトをインポートしてください(お使いでない方はこの部分は飛ばしてください)。
① 「Import Project」を選択し、play-api-sampleフォルダを指定
② 「Import Project from external model」からsbtを選択してNext
③ そのままFinish
##初期化
###バージョンの指定
Scala、sbt、Play Frameworkのバージョンが動作環境であげたものでなければ変更します。
基本的にインストールでsbt new
したときに自動で設定されるバージョンをそのまま使っていますが、タイミングによってはより新しいバージョンでインストールされる可能性があります。
そのまま使っていただいても構いませんが、当記事で記述したコードに対する動作確認は取れていないため、その旨ご了承ください。
name := """play-api-sample"""
organization := "com.example"
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayScala)
scalaVersion := "2.13.1"
libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test
sbt.version=1.3.8
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.0")
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")
###不要なファイルの削除
インストールした時点でサンプルコードがいくつか存在していますが、それらは今回使わないため削除します。
まず、ルーティングを初期化するためにconf/routes
ファイルの中身を空にします。
$ truncate -s0 conf/routes
次に、コントローラーのサンプルファイルとテストファイルを削除します。
$ rm ./app/controllers/HomeController.scala \
./test/controllers/HomeControllerSpec.scala
合わせて、Web APIには不要なフォルダを削除します。
/app/views
フォルダはサーバーサイドレンダリングに使用するフォルダで、/public
フォルダは公開アセット(画像ファイル・CSS・JavaScriptなど)を格納するフォルダです。
どちらも今回は使用しないため削除します。
$ rm -r ./app/views ./public
#最初の開発
ここからソースを編集していきます。
まず最初に、/hello
パスにGETでアクセスできるようにしてみます。
##テストコード実装
テストファーストの精神にのっとって、先にテストコードから書くことにします。
テストフレームワークにはScalaTest
をPlayFramework用に統合したscalatestplus-playを使用しています。1
test/controllers
配下にHelloControllerSpec.scala
を作成し、以下のように記述します。
$ touch ./test/controllers/HelloControllerSpec.scala
package controllers
import org.scalatestplus.play._
import org.scalatestplus.play.guice._
import play.api.test._
import play.api.test.Helpers._
class HelloControllerSpec extends PlaySpec with GuiceOneAppPerTest {
"HelloController GET" must {
"「/hello」にGETメソッドでアクセスできる" in {
val request = FakeRequest(GET, "/hello")
val response = route(app, request).get
status(response) mustBe OK
}
}
}
FakeRequest
オブジェクトを使うことで、HTTPメソッドとパスを指定して擬似的なリクエストを作成できます。
route
で、指定したパスの処理を呼びだしています。
status
で、レスポンスのHTTPステータスコードを取得しています。
OK
は単なる数値の200
を定数にしたもので、HTTPステータスコード200を表しています。
mustBe
メソッドで両者が同じものかどうかをテストしています。
(参考:Testing your application with ScalaTest)
ここではとりあえず200が返ってきさえすれば良しとします。
sbt test
でテストを実行してみます。
$ sbt test
(中略)
[info] HelloControllerSpec:
[info] HelloController GET
(※java9以降ではおそらくここでIllegal reflective accessの
警告が出力されますが、本筋ではなく実害もないため省略します)
[info] - must 「/hello」にGETメソッドでアクセスできる *** FAILED ***
[info] 404 was not equal to 200 (HelloControllerSpec.scala:19)
[info] ScalaTest
[info] Run completed in 2 seconds, 264 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
...
テストが失敗しました。
HTTPステータスが200
ではなく404
(NotFound)だと言われています。
このテストが通るように処理を記述します。
##プロダクションコード実装
/hello
にアクセスできるよう、conf/routes
を下記のように編集します。
GET /hello controllers.HelloController.hello
左から順に、HTTPメソッド
、パス
、そのパスにそのHTTPメソッドでアクセスした際に呼び出されるScalaのメソッド
です。2
パッケージはapp
フォルダ配下から始まるため、ここではapp/controllers/HelloController.scala
のhello
メソッドが呼び出されます。
なお、ここのパス名とメソッド名は一致していなくとも構いません。
app/controllers/
にhello
メソッドを持つHelloController.scala
を作成し、最低限動くように以下の記述をします。
$ touch ./app/controllers/HelloController.scala
package controllers
import javax.inject.Inject
import play.api.mvc._
class HelloController @Inject() (cc: ControllerComponents)
extends BaseControllerHelpers {
override protected def controllerComponents: ControllerComponents = cc
def hello(): Action[AnyContent] = {
val actionBuilder: ActionBuilder[Request, AnyContent] =
controllerComponents.actionBuilder
actionBuilder.apply(new Status(200))
}
}
Play FrameworkでControllerに指定するクラスは、BaseControllerHelpers
トレイトを継承します。
BaseControllerHelpers
トレイトを継承したクラスは、controllerComponents
メソッドを実装する必要があります。
trait BaseControllerHelpers extends ControllerHelpers {
protected def controllerComponents: ControllerComponents
(以下略)
controllerComponents
メソッドはControllerComponents
型の値を返します。
HelloController
クラスでは、 @Inject()(cc: ControllerComponents)
でDIされたオブジェクトをそのまま返しています。3
参考: Dependency Injection
helloメソッドが、実際に/hello
にGETでアクセスしたときに呼び出されるメソッドです。
def hello(): Action[AnyContent] = {
val actionBuilder: ActionBuilder[Request, AnyContent] =
controllerComponents.actionBuilder
actionBuilder.apply(new Status(200))
}
conf/routes
で指定されたメソッドは、 Action
型の値を返す必要があります(そうでない場合コンパイルエラーになります)。
Action
型の値を作るため、ActionBuilder
トレイトのapplyメソッドを使用します。
ここでは、ControllerComponents
トレイトに定義されているactionBuilder
メソッド経由でapplyメソッドを呼び出しています。
trait ControllerComponents {
def actionBuilder: ActionBuilder[Request, AnyContent]
...
controllerComponents.actionBuilder
の部分は直接cc.actionBuilder
と書くのと同じですが、後で少し加工するためにcontrollerComponents
メソッドを噛ませています。
new Status(200)
でHTTPステータスコード200を生成しています。
このStatus
クラスはResult
クラスを継承しており、ActionBuilder#apply
はResult
型の値からAction
を生成します。
この状態で、一度テストを実行してみます。
なお、毎回sbt test
と入力するとその度にsbtの起動から始まり時間がかかってしまうので、一度sbtシェルを立ち上げ、今後はこちらからテストを実行することとします。
$ sbt
(中略)
[play-api-sample] $ test
(中略)
[info] HelloControllerSpec:
[info] HelloController GET
(警告略)
[info] - must 「/hello」にGETメソッドでアクセスできる
[info] ScalaTest
[info] Run completed in 2 seconds, 157 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
...
テストが成功した(HTTPステータスコード200が返ってきた)ことがわかります。
#冗長な部分の省略
実は先のHelloController.scala
は、説明用にかなり冗長に書いています。
なので、簡略化出来る部分を簡略化していきます。
##継承元の変更
継承元をBaseControllerHelpers
トレイトからAbstractController
クラスに変更します。
AbstractController
クラスはBaseController
トレイトを継承しており、BaseController
トレイトがBaseControllerHelpers
トレイトを継承しています。
abstract class AbstractController(protected val controllerComponents: ControllerComponents) extends BaseController
trait BaseController extends BaseControllerHelpers {
...
AbstractController
クラスは、抽象メソッドであるcontrollerComponentsメソッド
を、controllerComponentsフィールド
経由で実装しています。4
controllerComponentsフィールド
は、HelloController
クラスからDI経由で渡されています。
そのため、HelloController
クラス内でcontrollerComponents
メソッドを実装する必要がなくなります。
##Actionメソッドの使用
BaseController
トレイトには、controllerComponents.actionBuilder
と同じ処理を行うAction
という名前のメソッドが定義されています。
trait BaseController extends BaseControllerHelpers {
def Action: ActionBuilder[Request, AnyContent] = controllerComponents.actionBuilder
}
そのため、helloメソッドは、下記のように書き換えられます。
def hello(): Action[AnyContent] = Action.apply(new Status(200))
ここで、左辺のAction[AnyContent]
と右辺のAction
が全く別物であることを理解しておいてください。
左は戻り値の型ですが、右は単なるcontrollerComponents.actionBuilder
のラッパーメソッドです。
Scalaにおいてapplyメソッドは省略可能なので、合わせてそちらも省略します。
def hello(): Action[AnyContent] = Action(new Status(200))
また、ステータスコード200は既にOk
という定数オブジェクトがResults.scala
に定義されているため、そちらを使います。
200だけでなく、主要なステータスコードは全て定義されています。5
def hello(): Action[AnyContent] = Action(Ok)
まとめると、`HelloController`は下記のように書き換えられます。
package controllers
import javax.inject.Inject
import play.api.mvc._
class HelloController @Inject() (cc: ControllerComponents)
extends AbstractController(cc) {
def hello(): Action[AnyContent] = Action(Ok)
}
sbtシェルでテストを再度実行し上記のコードでも動くことを確認します。
[play-api-sample] $ test
(中略)
[info] HelloControllerSpec:
[info] HelloController GET
(警告略)
[info] - must 「/hello」にGETメソッドでアクセスできる
[info] ScalaTest
[info] Run completed in 2 seconds, 247 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
...
問題なく成功しました。
今後はこちらの書き方をベースに進めていきます。
この時点で一度コミットしておきます。
sbtシェルが立ち上がっているので、別のターミナルからコミットします。
$ cd ~/xxx/play-api-sample/
$ git add .
$ git commit -m "「/hello」にGETメソッドでアクセスできる"
[master 9cfe817] 「/hello」にGETメソッドでアクセスできる
11 files changed, 29 insertions(+), 110 deletions(-)
...
これで、Play Frameworkを通してWeb APIにアクセスすることが出来るようになりました。
#レスポンスの指定
##文字列の取得
先程の処理ではHTTPステータスコードだけを指定していましたが、実際にはそれ以外にも何かしらのレスポンス(htmlやjsonなど)が返ってくるのを期待すると思います。
まずは、恒例の"Hello World"
文字列を取得できるようにします。
###テストコード実装
HelloControllerSpec
クラスのテストメソッドを、下記のように編集します。
package controllers
import org.scalatestplus.play._
import org.scalatestplus.play.guice._
import play.api.test.Helpers._
import play.api.test._
class HelloControllerSpec extends PlaySpec with GuiceOneAppPerTest {
"HelloController GET" must {
"「/hello」にGETメソッドでアクセスすると「Hello World」が返る" in {
val request = FakeRequest(GET, "/hello")
val response = route(app, request).get
status(response) mustBe OK
contentType(response) mustBe Some("text/plain")
contentAsString(response) mustBe "Hello World"
}
}
}
ステータスコードだけでなく、Content-Typeと中身の文字列(レスポンスボディ)もテストするようにしました。
この状態で実行すると、下記のように失敗します。
[play-api-sample] $ test
(中略)
[info] HelloControllerSpec:
[info] HelloController GET
(警告略)
[info] - must 「/hello」にGETメソッドでアクセスすると「Hello World」が返る *** FAILED ***
[info] None was not equal to Some("text/plain") (HelloControllerSpec.scala:17)
[info] ScalaTest
[info] Run completed in 2 seconds, 174 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
...
中身の文字列以前に、Content-Typeが指定されていないために失敗していることがわかります。
###プロダクションコード実装
テストが通るよう、HelloController
クラスを編集します。
package controllers
import javax.inject.Inject
import play.api.mvc._
class HelloController @Inject() (cc: ControllerComponents)
extends AbstractController(cc) {
def hello(): Action[AnyContent] = {
val result: Result = Ok("Hello World")
Action(result.as("text/plain"))
}
}
先ほどと違い、Okオブジェクトに"Hello World"
という文字列を渡しています。6
さらに、Result#as
メソッドによってContent-Typeを指定した上で、Actionを生成しています。
この状態でテストが通るかどうか確認してみます。
[play-api-sample] $ test
(中略)
[info] HelloControllerSpec:
[info] HelloController GET
(警告略)
[info] - must 「/hello」にGETメソッドでアクセスすると「Hello World」が返る
[info] ScalaTest
[info] Run completed in 2 seconds, 195 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
...
無事通りました。
Content-Typeがtext/plain
であること、およびレスポンスボディが"Hello World"
だったことが確認できました。
なお、実はレスポンスのContent-Typeは、レスポンスボディとして指定された値から自動的に推論されます。
そのため、as("text/plain")
の部分は省略可能です。
package controllers
import javax.inject.Inject
import play.api.mvc._
class HelloController @Inject() (cc: ControllerComponents)
extends AbstractController(cc) {
def hello(): Action[AnyContent] = Action(Ok("Hello World"))
}
[play-api-sample] $ test
(中略)
[info] HelloControllerSpec:
[info] HelloController GET
(警告略)
[info] - must 「/hello」にGETメソッドでアクセスすると「Hello World」が返る
[info] ScalaTest
[info] Run completed in 2 seconds, 152 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
...
##JSONの取得
昨今のWebサイトはSPA + Web APIで構成されることも増え、その場合Web API側はJSONを返すことが多いかと思います。
/hello
にPOSTメソッドでアクセスしたときはJSON(今回は固定値)が返ってくるようにしてみます。
###テストコード実装
HelloControllerSpec
に新たなテストを追加します。
package controllers
import org.scalatestplus.play._
import org.scalatestplus.play.guice._
import play.api.libs.json.Json
import play.api.test.Helpers._
import play.api.test._
class HelloControllerSpec extends PlaySpec with GuiceOneAppPerTest {
"HelloController GET" must {
"「/hello」にGETメソッドでアクセスすると「Hello World」が返る" in {
val request = FakeRequest(GET, "/hello")
val response = route(app, request).get
status(response) mustBe OK
contentType(response) mustBe Some("text/plain")
contentAsString(response) mustBe "Hello World"
}
}
"HelloController POST" must {
"「/hello」にPOSTメソッドでアクセスするとJsonが返る" in {
val request = FakeRequest(POST, "/hello")
val response = route(app, request).get
status(response) mustBe OK
contentType(response) mustBe Some("application/json")
contentAsJson(response) mustBe Json.obj(
"hello" -> "world",
"language" -> "scala"
)
}
}
}
「/hello」にPOSTメソッドでアクセスするとJSONが返る
というテストを追加しました。
JSONなのでContent-Typeはapplication/json
となるはずです。
返ってくるJSONの中身については今回は固定値とします。
JSONを扱うために、今回はplay.api.libs.json
のJSON用ライブラリを使います。
Json.obj
メソッドを使い、JSONに変換可能なオブジェクト(JsValue
)を生成します。
また、レスポンスボディにcontentAsJson
メソッドを適用することで、レスポンスボディから同じJSON用オブジェクトを生成します。
テストを実行すると、JSON以前に/hello
にPOSTメソッドでアクセスした際のルーティングが指定されてないと言われます。
[play-api-sample] $ test
(中略)
[info] HelloControllerSpec:
[info] HelloController GET
(警告略)
[info] - must 「/hello」にGETメソッドでアクセスすると「Hello World」が返る
[info] HelloController POST
[info] - must 「/hello」にPOSTメソッドでアクセスするとJsonが返る *** FAILED ***
[info] 404 was not equal to 200 (HelloControllerSpec.scala:29)
[info] ScalaTest
[info] Run completed in 2 seconds, 286 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
...
###プロダクションコード実装
conf/routes
に、/hello
にPOSTメソッドでアクセスした際のルーティング先を記述します。
ここでは、HelloController
のhelloJsonメソッドに紐付けることにします。
GET /hello controllers.HelloController.hello
POST /hello controllers.HelloController.helloJson
HelloController
にhelloJsonメソッドを追加します。
package controllers
import javax.inject.Inject
import play.api.libs.json._
import play.api.mvc._
class HelloController @Inject() (cc: ControllerComponents)
extends AbstractController(cc) {
def hello(): Action[AnyContent] = Action(Ok("Hello World"))
def helloJson(): Action[AnyContent] = Action {
val json: JsValue =
Json.obj("hello" -> "world", "language" -> "scala")
Ok(json)
}
}
テストコードと同様に固定値のJSONを返しています。
Content-Typeの記載はありませんが、レスポンスボディがJsValue
型の値の場合は自動的にapplication/json
が推論されます。
なお、ここではAction
の引数としてResult
型を返すブロック式を渡しています。
これまでのようにブロック式の最後でAction(Ok(json))
としても良いのですが、Ok
やNotFound
のようなステータスと比べ最後にAction
で包むのは半ば規定事項のため、このように引数部分だけをブロック式にした方がスッキリします。
今後は基本的にこの書き方で進めていきます。
この状態でテストを実行してみます。
[play-api-sample] $ test
(中略)
[info] HelloControllerSpec:
[info] HelloController GET
(警告略)
[info] - must 「/hello」にGETメソッドでアクセスすると「Hello World」が返る
[info] HelloController POST
[info] - must 「/hello」にPOSTメソッドでアクセスするとJsonが返る
[info] ScalaTest
[info] Run completed in 2 seconds, 246 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
...
無事、JSONを取得することが出来ました。
###HTTPieによる確認
値がちゃんとJSONになっているかどうか確かめるために、テストではなく実際に動かしてみます。
sbtシェルでrun
コマンドを実行します。
[play-api-sample] $ run
--- (Running the application, auto-reloading is enabled) ---
[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Enter to stop and go back to the console...)
別のターミナルを立ち上げ、HTTPieを使って実験します。7
まず下記のようにURLのみ指定した場合、デフォルトでGETメソッドとしてリクエストされます。
$ http localhost:9000/hello
HTTP/1.1 200 OK
Content-Length: 11
Content-Type: text/plain; charset=UTF-8
(中略)
Hello World
sbtシェルでテストした場合と同じく、レスポンスボディにHello World
が取得できていることがわかります。
次に、POSTメソッドでアクセスしてみます。
$ http POST localhost:9000/hello
HTTP/1.1 200 OK
Content-Length: 36
Content-Type: application/json
(中略)
{
"hello": "world",
"language": "scala"
}
...
レスポンスボディがJSON形式で返ってきていることが確認できました。
ここまでの成果を、一度コミットしておきます。
$ git add .
$ git commit -m "「/hello」にGETとPOSTでアクセスできる"
[master 3202dc6] 「/hello」にGETとPOSTでアクセスできる
3 files changed, 26 insertions(+), 4 deletions(-)
#最後に
とりあえず今回までで、指定のURLにリクエストを送信し、レスポンスを取得することはできました。
とはいえ、まだ現時点ではパラメータを渡したり、DBに接続してもいないので、実用性は皆無と言えます。
今後はそういった点を一つずつ実装していこうと思います。
また、現時点のソースをGitHubに上げてあります。
こちらも今後の実装に伴い都度更新していく予定です。
https://github.com/ka2kamaboko/play-api-sample.git
最後までお読み頂きありがとうございました。
質問や不備についてはコメント欄かTwitterまでお願いします。
-
最初にPlayFrameworkをインストールした時点で既にbuild.sbtに依存性が追加されています。 ↩
-
それぞれの項目が離れているのは単に今後ルーティングが増えた際に見た目を揃えるためだけです。最低空白が1つあれば動作上は問題ありません。 ↩
-
DI自体の説明は やはりあなた方のDependency Injectionはまちがっている。 がわかりやすかったです。 ↩
-
scalaの(private[this]でない)フィールドは実際はメソッド呼び出しなので、こういった記法が可能です。(参考: Scalaのvalとvarとdef) ↩
-
この
Ok
オブジェクトはHelloControllerSpec
に出てきた定数OK
とは別物です。後者は単なる数値型で、前者は後者をフィールドに持つStatus
クラスのインスタンスです。 ↩ -
これは実際には
Ok.apply("Hello World")
の略記法です。 ↩ -
curl
コマンドでも構いません。 ↩