【Play超入門】Play FrameworkでWeb APIを作る ~導入編~【Scala】

Play Framework(Scala)の入門記事です。

<br>
（19/12/07 20:38追記）

作業環境をWindowsからLinux(Linux Mint)に変更しました。
そのため、既存のPowerShell箇所のコマンド･出力をBashに変更しました。

----

最近Play Frameworkを勉強し始めたので、アウトプットがてらやったことを書き記して行こうと思います。
今回はとりあえずひな形をインストールして、GET/POSTでアクセスできるようになるまでです。

#はじめに
##Play Frameworkとは
軽量・ステートレス・非同期処理が特徴の、ScalaおよびJava用のWebフレームワークです。
[Play Framework - Build Modern &amp; Scalable Web Apps with Java and Scala](https://www.playframework.com/)

##作りたいもの
Play Frameworkを使用し、簡単なWeb APIを作ります。
サーバーサイドレンダリングは行いません。

##想定対象読者

- Scalaの基本的な文法を理解している方。
- Webの基本的な用語(GET/POST、リクエストヘッダー、Content-Typeなど)を理解している方。

##前提条件
自身のマシンに以下のものをインストールしておく必要があります。

- JDK(1.8またはそれ以降)
- ビルドツール(sbt または Gradle)

##動作環境
以下の環境で検証(テスト)しています。
また動作環境はテストが通ることを確認でき次第、随時更新していきます。

- [Linux Mint 19.3 Tricia 64bit (Cinnamon desktop)](https://www.linuxmint.com/download.php)
- [AdoptOpenJDK 11.0.7 HotSpot ](https://adoptopenjdk.net/)
- [Scala 2.13.1](https://www.scala-sbt.org/download.html)
- [sbt 1.3.8](https://www.scala-sbt.org/download.html)
- [Play Framework 2.8.0](https://www.playframework.com/)

エディタは[IntelliJ IDEA](https://www.jetbrains.com/idea/)を使用していますが、できるだけIDEに依存しない書き方をするつもりです。
お好きなエディタを使ってください。

それと途中経過を(自分用に)保存するために、Gitで途中途中コミットしています。

<del>なおOSがWindows10のため、コマンド類はPowerShellで実行しています。
MacやLinuxの方は適宜読み替えてください。</del>
（19/12/07 追記） Linuxに変更したためbashの記述に書き換えました。

また、各ファイルの文字コードはUTF-8で統一します。

#インストール･初期化
##インストール
sbtを使い、Play Frameworkのひな形をインストールします。
便宜上、ホームディレクトリ配下のxxxフォルダにインストールします。
実際には好きなフォルダを使ってください。
インストール用のコマンドは以下のとおりです。

```bash
~/xxx$ sbt new playframework/play-scala-seed.g8
```

上記コマンドを実行すると、nameとorganizationを求められます。
nameはここでは`play-api-sample`とします。
organizationは空欄のままEnterを押します(デフォルト値として｢com.example｣が設定されます)。

```bash
This template generates a Play Scala project

name [play-scala-seed]: play-api-sample
organization [com.example]:
```

- 補足<br>
上記操作をWindowsのPowerShell上で行った場合、sbt 1.3.0以降ではnameへの入力値が表示されません(不具合？)。<br>
入力自体はされているので、一通り入力してからEnterを押していただければ先に進めます。

なおWindowsの場合下記のようなエラーが出るようですが、調べた限りでは無視して良いようです。
参考: [Play Frameworkハンズオン環境構築 - Qiita](https://qiita.com/yuichi0301/items/4785e3fe490736d4ee50)

```powershell
[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)
...
```

<br>
play-api-sampleフォルダができているので、そのまま移動します。

```bash
~/xxx$ cd play-api-sample/
~/xxx/play-api-sample$ 
```

以降、カレントディレクトリを表す`~/xxx/play-api-sample`の部分は省略します。
フォルダ・ファイル構成としては以下のようになっています。

```bash
$ 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
```


###主要なフォルダ・ファイル
上記のフォルダ・ファイルの内、押さえておきたいものだけ説明します。
下記を含めた全体像を見たい場合は [公式ドキュメント](https://www.playframework.com/documentation/2.8.x/Anatomy) で確認してください。

- `build.sbt`: ビルドスクリプト
- `app`: アプリケーションのソースフォルダ
- `conf`: アプリケーションの設定ファイル用フォルダ
  - `application.conf`: スレッドプール、データベース、セキュリティなど、アプリケーションの基本的な設定ファイル
  - `routes`:  HTTPメソッド、リクエストパスに応じたルーティング設定ファイル
- `project`: sbt 設定ファイル群
  - `build.properties`: 使用する sbt のバージョンを宣言する指標的なファイル
  - `plugins.sbt`: Play 自身の定義を含む sbt プラグイン
- `test`: 単体、および機能テスト用のソースフォルダ

##初回実行
一度この状態で起動してみます。
`sbt run`コマンドを実行し、ブラウザで`localhost:9000`にアクセスします。

```bash
$ 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...)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492981/9eba66a8-ec25-cdba-45ce-ac154661fb70.png)

正常にアクセスできました。
「`Ctrl + C`」でsbtを終了します。

この時点で一度コミットしておきます。
なお、コミットメッセージに日本語を使うと表記がずれる可能性がありますが、Gitに関しては今回の主題ではないため取り上げません。

```bash
$ 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フォルダを指定
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492981/97f2cefd-0c16-19c8-a7be-e48d5133f0f3.png)

② 「Import Project from external model」からsbtを選択してNext
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492981/c5f22e51-42fd-62da-8448-004d3ea1e80f.png)
③ そのままFinish
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492981/9243323b-b892-69f5-5b3f-d714150e3fce.png)

##初期化
###バージョンの指定
Scala、sbt、Play Frameworkのバージョンが[動作環境](#動作環境)であげたものでなければ変更します。
基本的に[インストール](#インストール)で`sbt new`したときに自動で設定されるバージョンをそのまま使っていますが、タイミングによってはより新しいバージョンでインストールされる可能性があります。
そのまま使っていただいても構いませんが、当記事で記述したコードに対する動作確認は取れていないため、その旨ご了承ください。

```build.sbt
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
```

```project/build.properties
sbt.version=1.3.8
```

```project/plugins.sbt
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.0")
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.11.0")
```

###不要なファイルの削除
インストールした時点でサンプルコードがいくつか存在していますが、それらは今回使わないため削除します。

まず、ルーティングを初期化するために`conf/routes`ファイルの中身を空にします。

```bash
$ truncate -s0 conf/routes
```

次に、コントローラーのサンプルファイルとテストファイルを削除します。

```bash
$ rm ./app/controllers/HomeController.scala \
 ./test/controllers/HomeControllerSpec.scala
```

合わせて、Web APIには不要なフォルダを削除します。
`/app/views`フォルダはサーバーサイドレンダリングに使用するフォルダで、`/public`フォルダは公開アセット(画像ファイル・CSS・JavaScriptなど)を格納するフォルダです。
どちらも今回は使用しないため削除します。

```bash
$ rm -r ./app/views ./public
```


#最初の開発
ここからソースを編集していきます。

まず最初に、`/hello`パスにGETでアクセスできるようにしてみます。

##テストコード実装
テストファーストの精神にのっとって、先にテストコードから書くことにします。
テストフレームワークには`ScalaTest`をPlayFramework用に統合した[scalatestplus-play](https://github.com/playframework/scalatestplus-play.git)を使用しています。[^1]
`test/controllers`配下に`HelloControllerSpec.scala`を作成し、以下のように記述します。

```bash
$ touch ./test/controllers/HelloControllerSpec.scala
```

```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]( https://www.playframework.com/documentation/2.8.x/ScalaTestingWithScalaTest))

ここではとりあえず200が返ってきさえすれば良しとします。
`sbt test`でテストを実行してみます。

```bash
$ 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`を下記のように編集します。

```:conf/routes
GET     /hello                      controllers.HelloController.hello
```

左から順に、`HTTPメソッド`、`パス`、`そのパスにそのHTTPメソッドでアクセスした際に呼び出されるScalaのメソッド`です。[^2]

パッケージは`app`フォルダ配下から始まるため、ここでは`app/controllers/HelloController.scala`の`hello`メソッドが呼び出されます。
なお、ここのパス名とメソッド名は一致していなくとも構いません。

`app/controllers/`に`hello`メソッドを持つ`HelloController.scala`を作成し、最低限動くように以下の記述をします。

```bash
$ touch ./app/controllers/HelloController.scala
```

```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`メソッドを実装する必要があります。

```Controller.scala
trait BaseControllerHelpers extends ControllerHelpers {

  protected def controllerComponents: ControllerComponents

(以下略)
```

`controllerComponents`メソッドは`ControllerComponents`型の値を返します。
`HelloController`クラスでは、 `@Inject()(cc: ControllerComponents)`でDIされたオブジェクトをそのまま返しています。[^3]
参考: [Dependency Injection](https://www.playframework.com/documentation/2.8.x/ScalaDependencyInjection)


helloメソッドが、実際に`/hello`にGETでアクセスしたときに呼び出されるメソッドです。

```app/controllers/HelloController.scala
  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メソッドを呼び出しています。

```Controller.scala
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シェルを立ち上げ、今後はこちらからテストを実行することとします。

```bash
$ 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`トレイトを継承しています。

```Controller.scala
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`という名前のメソッドが定義されています。

```Controller.scala
trait BaseController extends BaseControllerHelpers {
  def Action: ActionBuilder[Request, AnyContent] = controllerComponents.actionBuilder
}
```
そのため、helloメソッドは、下記のように書き換えられます。

```app/controllers/HelloController.scala
  def hello(): Action[AnyContent] = Action.apply(new Status(200))
```

ここで、左辺の`Action[AnyContent]`と右辺の`Action`が全く別物であることを理解しておいてください。
左は戻り値の型ですが、右は単なる`controllerComponents.actionBuilder`のラッパーメソッドです。

Scalaにおいてapplyメソッドは省略可能なので、合わせてそちらも省略します。

```app/controllers/HelloController.scala
  def hello(): Action[AnyContent] = Action(new Status(200))
```

また、ステータスコード200は既に`Ok`という定数オブジェクトが`Results.scala`に定義されているため、そちらを使います。
200だけでなく、主要なステータスコードは全て定義されています。[^5]

```app/controllers/HelloController.scala
  def hello(): Action[AnyContent] = Action(Ok)
```

<br>
まとめると、`HelloController`は下記のように書き換えられます。

```app/controllers/HelloController.scala
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シェルでテストを再度実行し上記のコードでも動くことを確認します。

```bash
[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シェルが立ち上がっているので、別のターミナルからコミットします。

```bash
$ 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`クラスのテストメソッドを、下記のように編集します。

```test/controllers/HelloControllerSpec.scala
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と中身の文字列(レスポンスボディ)もテストするようにしました。
この状態で実行すると、下記のように失敗します。

```bash
[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`クラスを編集します。

```app/controllers/HelloController.scala
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を生成しています。

この状態でテストが通るかどうか確認してみます。

```bash
[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")`の部分は省略可能です。

```app/controllers/HelloController.scala
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"))
}
```

```bash
[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`に新たなテストを追加します。

```test/controllers/HelloControllerSpec.scala
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メソッドでアクセスした際のルーティングが指定されてないと言われます。

```bash
[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メソッドに紐付けることにします。

```conf/routes
GET     /hello                      controllers.HelloController.hello
POST    /hello                      controllers.HelloController.helloJson
```

`HelloController`にhelloJsonメソッドを追加します。

```app/controllers/HelloController.scala
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`で包むのは半ば規定事項のため、このように引数部分だけをブロック式にした方がスッキリします。
今後は基本的にこの書き方で進めていきます。

この状態でテストを実行してみます。

```bash
[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`コマンドを実行します。

```bash
[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](https://github.com/jakubroztocil/httpie)を使って実験します。[^7]


まず下記のようにURLのみ指定した場合、デフォルトでGETメソッドとしてリクエストされます。

```bash
$ 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メソッドでアクセスしてみます。

```bash
$ http POST localhost:9000/hello
HTTP/1.1 200 OK
Content-Length: 36
Content-Type: application/json

(中略)

{
    "hello": "world",
    "language": "scala"
}
...
```
レスポンスボディがJSON形式で返ってきていることが確認できました。

ここまでの成果を、一度コミットしておきます。

```bash
$ 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](https://twitter.com/ka2_kamaboko)までお願いします。

[^1]: 最初にPlayFrameworkをインストールした時点で既にbuild.sbtに依存性が追加されています。
[^2]: それぞれの項目が離れているのは単に今後ルーティングが増えた際に見た目を揃えるためだけです。最低空白が1つあれば動作上は問題ありません。
[^3]: DI自体の説明は [やはりあなた方のDependency Injectionはまちがっている。](http://blog.a-way-out.net/blog/2015/08/31/your-dependency-injection-is-wrong-as-I-expected/) がわかりやすかったです。
[^4]: scalaの(private[this]でない)フィールドは実際はメソッド呼び出しなので､こういった記法が可能です｡(参考: [Scalaのvalとvarとdef](http://hiratara.hatenadiary.jp/entry/20120202/1328155695))
[^5]: この`Ok`オブジェクトは`HelloControllerSpec`に出てきた定数`OK`とは別物です。後者は単なる数値型で、前者は後者をフィールドに持つ`Status`クラスのインスタンスです。
[^6]: これは実際には`Ok.apply("Hello World")`の略記法です。
[^7]: `curl`コマンドでも構いません。