LoginSignup
63
47

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-09-09

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)

動作環境

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

エディタは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...)

image.png

正常にアクセスできました。
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フォルダを指定
無題.png

② 「Import Project from external model」からsbtを選択してNext
image.png
③ そのままFinish
image.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ファイルの中身を空にします。

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

conf/routes
GET     /hello                      controllers.HelloController.hello

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

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

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

$ 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

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#applyResult型の値から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トレイトを継承しています。

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)



まとめると、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シェルでテストを再度実行し上記のコードでも動くことを確認します。

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

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

[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を生成しています。

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

[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"))
}
[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メソッドでアクセスした際のルーティングが指定されてないと言われます。

[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メソッドを追加します。

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))としても良いのですが、OkNotFoundのようなステータスと比べ最後に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までお願いします。


  1. 最初にPlayFrameworkをインストールした時点で既にbuild.sbtに依存性が追加されています。 

  2. それぞれの項目が離れているのは単に今後ルーティングが増えた際に見た目を揃えるためだけです。最低空白が1つあれば動作上は問題ありません。 

  3. DI自体の説明は やはりあなた方のDependency Injectionはまちがっている。 がわかりやすかったです。 

  4. scalaの(private[this]でない)フィールドは実際はメソッド呼び出しなので、こういった記法が可能です。(参考: Scalaのvalとvarとdef

  5. このOkオブジェクトはHelloControllerSpecに出てきた定数OKとは別物です。後者は単なる数値型で、前者は後者をフィールドに持つStatusクラスのインスタンスです。 

  6. これは実際にはOk.apply("Hello World")の略記法です。 

  7. curlコマンドでも構いません。 

63
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
47